From 6a31d875c72c5a0dccfd7b9521af6ecfa02297b1 Mon Sep 17 00:00:00 2001 From: Anton Osika Date: Tue, 30 May 2023 08:39:38 +0200 Subject: [PATCH] Clean up clarifying question part --- README.md | 8 +++ example/main_prompt | 86 ++++++++++++++++++++++++++++ identity/clarify | 5 -- identity/qa | 7 +-- identity/setup | 4 +- identity/use_qa | 13 +++++ main.py | 28 ++++----- requirements.txt | 2 + scripts/rerun_edited_message_logs.py | 48 ++++++++++++++++ steps.py | 37 +++++++----- 10 files changed, 195 insertions(+), 43 deletions(-) create mode 100644 example/main_prompt delete mode 100644 identity/clarify create mode 100644 identity/use_qa create mode 100644 requirements.txt create mode 100644 scripts/rerun_edited_message_logs.py diff --git a/README.md b/README.md index 51d194d..b409f46 100644 --- a/README.md +++ b/README.md @@ -1 +1,9 @@ # gpt-engineer + +How to use: + +- Install requirements.txt `pip install -r requirements.txt` +- Copy the example folder `cp example -r my-new-project` +- Edit the file main_prompt in my-new-project +- run `python main.py my-new-prompt` +- Check the results in my-new-project/workspace diff --git a/example/main_prompt b/example/main_prompt new file mode 100644 index 0000000..8926955 --- /dev/null +++ b/example/main_prompt @@ -0,0 +1,86 @@ +Instructions: +We are writing a feature computation framework. + +It will mainly consist of FeatureBuilder classes. + +Each Feature Builder will have the methods: +- get(key, config, context, cache): Call feature builder dependencies and then compute the feature. Returns value and hash of value. + - key: tuple of arguments that are used to compute the feature + - config: the configuration for the feature + - context: dataclass that contains dependencies and general configuration (see below) + - controller: object that can be used to get other features (see below) + - value: object that can be pickled + +It will have the class attr: +- deps: list of FeatureBuilder classes +- default_config: function that accepts context and returns a config + +The Controller will have the methods: +- get(feature_builder, key, config): Check the cache, and decide to call feature builder and then returns the output and timestamp it was computed + - feature_builder: FeatureBuilder class + - key: tuple of arguments that are used to compute the feature + - configs: dict of configs that are used to compute features + +and the attributes: +- context: dataclass that contains dependencies and general configuration (see below) +- cache: cache for the features + +Where it is unclear, please make assumptions and add a comment in the code about it + +Here is an example of Builders we want: + +ProductEmbeddingString: takes product_id, queries the product_db and gets the title as a string +ProductEmbedding: takes string and returns and embedding +ProductEmbeddingDB: takes just `merchant` name, uses all product_ids and returns the blob that is a database of embeddings +ProductEmbeddingSearcher: takes a string, constructs embeddingDB feature (note: all features are cached), embeds the string and searches the db +LLMProductPrompt: queries the ProductEmbeddingString, and formats a template that says "get recommendations for {title}" +LLMSuggestions: Takes product_id, looks up prompts and gets list of suggestions of product descriptions +LLMLogic: Takes the product_id, gets the LLM suggestions, embeds the suggestions, does a search, and returns a list of product_ids + + +The LLMLogic is the logic_builder in a file such as this one: +``` +def main(merchant, market): + cache = get_feature_cache() + interaction_data_db = get_interaction_data_db() + product_db = get_product_db() + merchant_config = get_merchant_config(merchant) + + context = Context( + interaction_data_db=interaction_data_db, + product_db=product_db, + merchant_config=merchant_config, + ) + + product_ids = cache(ProductIds).get( + key=(merchant, market), + context=context, + cache=cache, + ) + + for logic_builder in merchant_config['logic_builders']: + for product_id in product_ids: + key = (merchant, market, product_id) + p2p_recs = cache(logic_builder).get(key=key, context=context, cache=cache) + redis.set(key, p2p_recs) +``` + +API to product_db: +```python + async def get_product_attribute_dimensions( + self, + ) -> dict[AttributeId, Dimension]: + pass + + async def get_products( + self, + attribute_ids: set[AttributeId], + product_ids: set[ProductId] | None = None, + ) -> dict[ProductId, dict[AttributeId, dict[IngestionDimensionKey, Any]]]: + pass +``` +(note, dimensions are not so important. They related to information that varies by: locale, warehouse, pricelist etc) + +--- +You will focus on writing the integration test file test_all.py. +This file will Mock a lot of the necessary interfaces, run the logic LLMLogic and print the results from it. diff --git a/identity/clarify b/identity/clarify deleted file mode 100644 index 999daba..0000000 --- a/identity/clarify +++ /dev/null @@ -1,5 +0,0 @@ -You will improve instructions by reading: -1. ORIGINAL INSTRUCTIONS -2. CLARIFYING QUESTIONS AND ANSWERS - -As output you will give a new version of the original instruction but where the answers to the clarifying questions have been incorporated to make it completely clear. diff --git a/identity/qa b/identity/qa index ab5acdb..f4f9934 100644 --- a/identity/qa +++ b/identity/qa @@ -1,4 +1,3 @@ -You will read instructions and NOT carry them out, only seek to CLARIFY them. -You will carry out the steps: -1. Write a list of super short bullets of areas that are unclear -2. Ask for only one clarifying questions and wait for a reply \ No newline at end of file +You will read instructions and not carry them out, only seek to clarify them. +Specifically you will first summarise a list of super short bullets of areas that need clarification. +Then you will pick one clarifying question, and wait for an answer from the user. diff --git a/identity/setup b/identity/setup index cb8bac6..4a0d841 100644 --- a/identity/setup +++ b/identity/setup @@ -10,6 +10,6 @@ Before you finish, double check that all parts of the architecture is present in File syntax: -```main_file.py +```file.py [ADD YOUR CODE HERE] -``` \ No newline at end of file +``` diff --git a/identity/use_qa b/identity/use_qa new file mode 100644 index 0000000..cb164c6 --- /dev/null +++ b/identity/use_qa @@ -0,0 +1,13 @@ +Please now remember the steps: + +First lay out the names of the core classes, functions, methods that will be necessary, As well as a quick comment on their purpose. +Then output the content of each file, with syntax below. +(You will start with the "entrypoint" file, then go to the ones that are imported by that file, and so on.) +Make sure that files contain all imports, types etc. The code should be fully functional. Make sure that code in different files are compatible with each other. +Before you finish, double check that all parts of the architecture is present in the files. + +File syntax: + +```main_file.py +[ADD YOUR CODE HERE] +``` diff --git a/main.py b/main.py index 3c0af66..772f617 100644 --- a/main.py +++ b/main.py @@ -11,33 +11,26 @@ import typer app = typer.Typer() - @app.command() def chat( + project_path: str = typer.Argument(None, help="path"), + run_prefix: str = typer.Option("", help="run prefix"), model: str = "gpt-4", temperature: float = 0.1, max_tokens: int = 4096, n: int = 1, stream: bool = True, - input_path: str = typer.Argument( - None, help="input path" - ), - memory_path: str = typer.Argument( - None, help="memory path" - ), - workspace_path: Optional[str] = typer.Option( - None, "--out", "-c", help="Code to file path" - ), ): - if memory_path is None: - memory_path = pathlib.Path(__file__).parent / 'memory' + if project_path is None: + project_path = str(pathlib.Path(__file__).parent / "example") + + input_path = project_path + memory_path = pathlib.Path(project_path) / "memory" + workspace_path = pathlib.Path(project_path) / (run_prefix + "workspace") - if input_path is None: - input_path = pathlib.Path(__file__).parent / 'input' - ai = AI( model=model, temperature=temperature, @@ -49,13 +42,12 @@ def chat( dbs = DBs( memory=DB(memory_path), - logs=DB(pathlib.Path(memory_path) / 'logs'), + logs=DB(pathlib.Path(memory_path) / "logs"), input=DB(input_path), workspace=DB(workspace_path), - identity=DB(pathlib.Path(__file__).parent / 'identity'), + identity=DB(pathlib.Path(__file__).parent / "identity"), ) - run_prefix= workspace_path.split('/')[-1] + '_' if workspace_path is not None else '' for step in STEPS: messages = step(ai, dbs) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..02c40f6 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +openai +typer diff --git a/scripts/rerun_edited_message_logs.py b/scripts/rerun_edited_message_logs.py new file mode 100644 index 0000000..feb3299 --- /dev/null +++ b/scripts/rerun_edited_message_logs.py @@ -0,0 +1,48 @@ +import json +import os +import pathlib +from typing import Optional +import openai +from chat_to_files import to_files +from ai import AI +from steps import STEPS +from db import DB, DBs +import typer + + +app = typer.Typer() + + +@app.command() +def chat( + messages_path: str, + out_path: str | None = None, + model: str = "gpt-4", + temperature: float = 0.1, + max_tokens: int = 4096, + n: int = 1, + stream: bool = True, +): + + + ai = AI( + model=model, temperature=temperature, + max_tokens=max_tokens, + n=n, + stream=stream, + stop=None, + ) + + with open(messages_path) as f: + messages = json.load(f) + + messages = ai.next(messages) + + if out_path: + to_files(messages[-1]['content'], out_path) + with open(pathlib.Path(out_path) / 'all_output.txt', 'w') as f: + json.dump(messages[-1]['content'], f) + + +if __name__ == "__main__": + app() diff --git a/steps.py b/steps.py index 7d59df0..3b662ae 100644 --- a/steps.py +++ b/steps.py @@ -3,6 +3,7 @@ from dataclasses import dataclass from typing import Callable from ai import AI from chat_to_files import to_files +import json from db import DBs from db import DB @@ -15,36 +16,44 @@ def setup(ai: AI, dbs: DBs): to_files(messages[-1]['content'], dbs.workspace) return messages -def run_clarified(ai: AI, dbs: DBs): - messages = ai.start(setup_sys_prompt(dbs), dbs.input['main_prompt']) - to_files(messages[-1]['content'], DB(str(dbs.workspace.path)+'_clarified')) - return messages - def clarify(ai: AI, dbs: DBs): messages = [ai.fsystem(dbs.identity['qa'])] user = dbs.input['main_prompt'] while True: messages = ai.next(messages, user) + + if messages[-1]['content'].strip().lower() == 'no': + break + print() user = input('Answer: ') if not user or user == 'q': break - user += '\nIs anything else unclear? Please ask more questions until instructions are sufficient to write the code.' + user += '\n\nIs anything else unclear? If everything is sufficiently clear to write the code, just answer "no".' - # TOOD: Stop using clarify prompt. Just append questions and answers to the main prompt. - prompt = dbs.identity['clarify'] - messages = ai.next([ai.fsystem(prompt)] + messages[1:], prompt) - dbs.memory['clarified_prompt'] = messages[-1]['content'] + return messages + +def run_clarified(ai: AI, dbs: DBs): + # get the messages from previous step + messages = json.loads(dbs.logs[clarify.__name__]) + + messages = ( + [ + ai.fsystem(setup_sys_prompt(dbs)), + ] + + messages[1:] + ) + messages = ai.next(messages, dbs.identity['use_qa']) + to_files(messages[-1]['content'], DB(str(dbs.workspace.path)+'_clarified')) return messages # STEPS: List[Callable[[AI, DBs], List]] = [ STEPS=[ - setup, - # clarify, - # run_clarified - # to_files, + # setup, + clarify, + run_clarified # improve_files, # run_tests, # ImproveBasedOnHumanComments