Merge branch 'main' into ntindle/agpt-401-add-codex-client-to-manage-all-codex-state-in-discord-bot

This commit is contained in:
Nicholas Tindle committed 2024-02-27 07:12:24 -06:00
commit 0ed47be9eb
7 files changed
+448 -33

No files matched your search

+3 -1
View File
@@ -2,6 +2,7 @@ import asyncio
import logging
import os
from prisma.enums import FunctionState
from prisma.models import APIRouteSpec, CompletedApp, Function, Specification
from codex.api_model import Identifiers
@@ -78,7 +79,7 @@ async def develop_route(
invoke_params={
"function_name": function_name,
"goal": goal_description,
"description": function_template or "",
"function_signature": function_template,
"provided_functions": [
f.template
for f in generated_functions
@@ -107,6 +108,7 @@ async def develop_route(
generated_functions=generated_functions,
)
for child in route_function.ChildFunction
if child.state == FunctionState.DEFINITION
]
await asyncio.gather(*tasks)
else:
+43 -9
View File
@@ -67,6 +67,7 @@ class FunctionVisitor(ast.NodeVisitor):
def __init__(self):
self.functions = {}
self.imports = []
self.pydantic_classes = []
def visit_Import(self, node):
for alias in node.names:
@@ -84,6 +85,10 @@ class FunctionVisitor(ast.NodeVisitor):
self.imports.append(import_line)
self.generic_visit(node)
# TODO(AGPT-425):
# - Add visit_ClassDef and parse input output complex types
# - Exclude function inside the class and 'unexpected' nested functions.
def visit_FunctionDef(self, node: ast.FunctionDef) -> None:
args = []
for arg in node.args.args:
@@ -92,31 +97,56 @@ class FunctionVisitor(ast.NodeVisitor):
args_str = ", ".join(args)
return_type = ast.unparse(node.returns) if node.returns else "Unknown"
# Extracting the function_template with doc_string if it exists
original_body = node.body.copy()
# Extract doc_string & function body
pass_block = ast.parse("pass").body[0]
if (
node.body
and isinstance(node.body[0], ast.Expr)
and isinstance(node.body[0].value, (ast.Str, ast.Constant))
):
node.body = node.body[:1]
doc_string_body = [node.body[0], pass_block]
code_body = node.body[1:]
else:
node.body = []
doc_string_body = [pass_block]
code_body = node.body
# add "pass" statement to the node.body
node.body.append(ast.parse("pass").body[0])
# Construct function template
original_body = node.body.copy()
node.body = doc_string_body
function_template = ast.unparse(node)
node.body = original_body
# Function is not implemented if it has pass_block as its body
is_implemented = len(code_body) > 1 or ast.unparse(code_body[0]) != ast.unparse(
pass_block
)
self.functions[node.name] = FunctionDef(
name=node.name,
function_template=function_template,
args=args_str,
return_type=return_type,
is_implemented=is_implemented,
function_template=function_template,
function_code=ast.unparse(node),
)
self.generic_visit(node)
def visit_ClassDef(self, node: ast.ClassDef) -> None:
"""
Visits a ClassDef node in the AST and checks if it is a Pydantic class.
If it is a Pydantic class, adds its name to the list of Pydantic classes.
"""
for base in node.bases:
base_id = getattr(base, "id", None)
# This will only work for direct children of BaseModel
is_pydantic = (isinstance(base, ast.Name) and base.id == "BaseModel") or (
isinstance(base, ast.Attribute) and base.attr == "BaseModel"
)
if is_pydantic:
self.pydantic_classes.append(node.name)
self.generic_visit(node)
class DevelopAIBlock(AIBlock):
developement_phase: DevelopmentPhase = DevelopmentPhase.DEVELOPMENT
@@ -165,7 +195,7 @@ class DevelopAIBlock(AIBlock):
invoke_params["function_name"]
]
if "pass" in requested_func.function_code:
if not requested_func.is_implemented:
# Important: ValidationErrors are used in the retry prompt
raise ValidationError(
"Main Function body is empty, it should contain"
@@ -211,7 +241,11 @@ class DevelopAIBlock(AIBlock):
functionName=value.name,
template=value.function_template,
apiRouteSpecId=generated_response.api_route_spec.id,
state=FunctionState.DEFINITION,
state=FunctionState.WRITTEN
if value.is_implemented
else FunctionState.DEFINITION,
rawCode=value.function_code,
functionCode=value.function_code,
)
function_defs.append(model)
+1
View File
@@ -15,6 +15,7 @@ class FunctionDef(BaseModel):
name: str
args: str
return_type: str
is_implemented: bool
function_template: str
function_code: str
@@ -82,6 +82,8 @@ def convert_to_markdown(html: str) -> str:
def convert_webpages(urls: List[str], format: str) -> List[str]:
"""
~ THIS IS THE REQUESTED FUNCTION, SO YOU HAVE TO FULLY IMPLEMENT THIS FUNCTION ~
Converts a list of webpages to a specified format.
This function takes a list of URLs and downloads the HTML content of each webpage.
@@ -117,13 +119,19 @@ def convert_webpages(urls: List[str], format: str) -> List[str]:
Thinking carefully step by step. Always start your answer with your analysis of the problem and possible problems. Then discuss the types of objects that maybe useful
## IMPORTANT
1. USE A SINGLE CODE BLOCK ("```python") FOR ALL PYTHON CODE (THIS IS MANDATORY)
2. THE GENERATED CODE HAS TO BE A WORKING CODE, NO SIMPLIFICATIONS OR INCOMPLETE CODE, UNIMPLEMENTED FUNCTIONS MUST BE STUBS.
3. CREATE STUB ONLY IF IT'S REALLY NECESSARY! TRY TO KEEP ONLY A SINGLE FUNCTION. KEEP THE CODE AS SIMPLE AS POSSIBLE WITH LESS STUBS AND ABSTRACTIONS.
4. ONLY PROVIDE THE STUB FUNCTIONS USED IN THE IMPLEMENTED FUNCTION, NO NEED TO PROVIDE THE OTHER STUB FUNCTIONS, JUST THE REQUESTED FUNCTION AND THE STUB FUNCTIONS.
5. USE A SINGLE REQUIREMENTS BLOCK ("```requirements") FOR ALL REQUIREMENTS CODE (THIS IS MANDATORY EVEN IF THERE ARE NO REQUIREMENTS)
6. IF THE FUNCTION USE NON-CORE PYTHON LIBS, ADD THE IMPORTS STATEMENTS, AND ADD THE LIBS TO THE REQUIREMENTS BLOCK.
7. YOU ARE CONTINUING AN ONGOING WORK, SO YOU ONLY NEED TO IMPLEMENT THE FUNCTIONALITY THAT IS REQUESTED, DON'T IMPLEMENT UNREQUESTED FUNCTIONALITY.
8. DOC STRING ON EACH FUNCTION AND STUB IS MANDATORY AND AS DETAILED AS POSSIBLE! ALWAYS DEFINE: Function description, `Args`, `Returns`, and `Example`, EVEN THE STUBS. FOR STUBS, THE LONGER THE DOC STRING, THE BETTER.
9. DO NOT CHANGE THE SIGNATURE OF THE FUNCTION, THE FUNCTION NAME, OR THE PARAMETER NAMES. THE FUNCTIONALITY MUST BE THE SAME AS THE ORIGINAL FUNCTION.
10. AVOID NESTED FUNCTIONS, THE STUBS AND FUNCTIONS MUST BE IN THE GLOBAL SCOPE.
* USE A SINGLE CODE BLOCK ("```python") FOR ALL PYTHON CODE, USE A SINGLE REQUIREMENTS BLOCK ("```requirements") FOR ALL REQUIREMENTS CODE (THIS IS MANDATORY)
{% if allow_stub %}
* THE GENERATED CODE HAS TO BE A WORKING CODE, NO SIMPLIFICATIONS OR INCOMPLETE CODE, UNIMPLEMENTED FUNCTIONS MUST BE STUBS.
* CREATE STUB ONLY IF IT'S REALLY NECESSARY! TRY TO KEEP ONLY A SINGLE FUNCTION. KEEP THE CODE AS SIMPLE AS POSSIBLE WITH LESS STUBS AND ABSTRACTIONS.
* ONLY PROVIDE THE STUB FUNCTIONS USED IN THE IMPLEMENTED FUNCTION, NO NEED TO PROVIDE THE OTHER STUB FUNCTIONS, JUST THE REQUESTED FUNCTION AND THE STUB FUNCTIONS.
* IMPORTANT: ALWAYS USE `pass` KEYWORD IN THE STUBS, DO NOT RETURN DUMMY VALUES, OR USE `...` OR `None` OR RAISE EXCEPTIONS. THE STUBS MUST BE EMPTY FUNCTIONS WITH DOC STRINGS and `pass` KEYWORD.
{% else %}
* IMPORTANT: FOR THIS GENERATION, CREATE ONLY ONE FUNCTION WITHOUT ANY STUBS. THE GENERATED FUNCTION HAS TO BE A WORKING CODE, NO SIMPLIFICATIONS OR INCOMPLETE CODE.
{% endif %}
* IF THE FUNCTION USE NON-CORE PYTHON LIBS, ADD THE IMPORTS STATEMENTS, AND ADD THE LIBS TO THE REQUIREMENTS BLOCK.
* YOU ARE CONTINUING AN ONGOING WORK, SO YOU ONLY NEED TO IMPLEMENT THE FUNCTIONALITY THAT IS REQUESTED, DON'T IMPLEMENT UNREQUESTED FUNCTIONALITY.
* DOC STRING ON EACH FUNCTION AND STUB IS MANDATORY AND AS DETAILED AS POSSIBLE! ALWAYS DEFINE: Function description, `Args`, `Returns`, and `Example`, EVEN THE STUBS. FOR STUBS, THE LONGER THE DOC STRING, THE BETTER.
* DO NOT CHANGE THE SIGNATURE OF THE FUNCTION, THE FUNCTION NAME, OR THE PARAMETER NAMES. THE FUNCTIONALITY MUST BE THE SAME AS THE ORIGINAL FUNCTION. IF THE REQUESTED FUNCTION IS ALREADY IMPLEMENTED, CHANGE IT ONLY IF IT'S REALLY NECESSARY.
* AVOID NESTED FUNCTIONS, THE STUBS AND FUNCTIONS MUST BE IN THE GLOBAL SCOPE.
* ONLY WRITE FUNCTIONS NOT CLASS, UNLESS IT'S DATA CLASS OR NAMEDTUPLE.
* NEVER USE DUMMY DATA OR GLOBAL VARIABLES! ALWAYS PASS WHAT'S NEEDED AS ARGUMENTS! NO HARDCODED VALUES!
@@ -1,12 +1,17 @@
Main function name: {{ function_name }}
Main function detail:
{{ description }}
Create the working code implementation of function called: {{ function_name }}
{% if function_signature %}
With this function signature:
```python
{{ function_signature }}
```
The created function has to match exactly as this signature!
{% endif %}
For additional context, this function that you need to implement will be used as part of a larger program for this goal:
"{{ goal }}".
The signature has to match exactly as the one in the description.
For additional context, this function that you need to implement will be used as part of a larger program for {{ goal }}.
----
NOTE: IMPLEMENT THE REQUIRED FUNCTION WITH A REAL CODE IMPLEMENTATION, NOT JUST A STUB, PLACEHOLDER, OR PSEUDOCODE!
{% if provided_functions %}
----
You can re-use these functions without introducing a new stub:
```python
{% for function in provided_functions %}
@@ -17,8 +22,5 @@ You can re-use these functions without introducing a new stub:
NOTE:
DO NOT RE-WRITE THESE FUNCTIONS. JUST CALL IT IN THE FUNCTION THAT YOU WRITE (IF NEEDED).
YOU SHOULD ONLY PRODUCE THE REQUIRED FUNCTION AND THE NEW STUBS (IF THERE IS ANY).
{% endif %}
{% if not allow_stub %}
IMPORTANT: FOR THIS GENERATION, CREATE ONLY ONE FUNCTION WITHOUT ANY STUBS.
----
{% endif %}
+368
View File
@@ -0,0 +1,368 @@
import ast
import pytest
from codex.develop.develop import FunctionVisitor
SAMPLE_CODE = """
from pydantic import BaseModel
from datetime import datetime, timedelta
from typing import Dict, List, Tuple
class Location(BaseModel):
name: str # For named locations or coordinates as a string
class ProfessionalWorkingHours(BaseModel):
professional_id: str
start: datetime
end: datetime
class Appointment(BaseModel):
start_time: datetime
end_time: datetime
professional_id: str
location: str
class TravelTime(BaseModel):
start_location: str
end_location: str
time: timedelta
class SomeClass:
start_location: str
end_location: str
time: timedelta
def calculate_travel_time(start_location: Location, end_location: Location) -> timedelta:
\"\"\"
Stub function to calculate the travel time between two locations.
This function is intended to compute the estimated time it takes to travel from a start location to an end location.
It can accept either named locations (e.g., "Office", "Home") or geographical coordinates in string format (e.g., "40.7128,-74.0060").
The actual implementation of this function would involve complex logic possibly including API calls to mapping services,
but in this stub, the specifics of how travel time is calculated are abstract.
Args:
start_location (str): The starting location's name or coordinates.
end_location (str): The ending location's name or coordinates.
Returns:
timedelta: The estimated travel time between the two locations. For the purposes of this stub, a fixed
travel time is returned to simulate functionality.
Example:
calculate_travel_time("Office", "Client Site")
> timedelta(minutes=30)
\"\"\"
pass
def is_within_working_hours(
professional_id: str,
proposed_time: datetime,
working_hours: Dict[str, ProfessionalWorkingHours]
) -> bool:
\"\"\"
Determination whether a proposed appointment time falls within a professional's predefined working hours.
This function assesses the suitability of a proposed appointment time by comparing it with the professional's working
hours. It is particularly useful in scheduling systems where appointments need to be set during specific hours.
Args:
professional_id (str): Unique identifier of the professional.
proposed_time (datetime): The datetime object representing the proposed appointment time.
working_hours (Dict[str, Tuple[datetime, datetime]]): A dictionary with professional_id as keys and tuples
representing the start and end of working hours as values.
Returns:
bool: A boolean value indicating if the proposed_time falls within the working hours of the specified professional.
Example:
is_within_working_hours(
"prof123",
datetime(2023, 4, 15, 10, 0),
{"prof123": (datetime(2023, 4, 15, 9, 0), datetime(2023, 4, 15, 17, 0))}
)
> True
\"\"\"
pass
def create_schedule(
appointments: List[Appointment],
working_hours: Dict[str, ProfessionalWorkingHours],
travel_times: Dict[Tuple[str, str], TravelTime]
) -> List[Appointment]:
suggested_appointments = []
for prof_id, working_hour in working_hours.items():
available_start = working_hour.start
while available_start < working_hour.end:
# Find end time by adding minimum appointment duration to start
available_end = available_start + timedelta(hours=1) # Assuming 1-hour appointments
# Check if this slot is within working hours and not overlapping with existing appointments
if is_within_working_hours(prof_id, available_start, working_hours) and all(
not (appointment.start_time <= available_start < appointment.end_time or
appointment.start_time < available_end <= appointment.end_time)
for appointment in appointments if appointment.professional_id == prof_id
):
# Assuming all appointments are at the same location for simplification
# In a real scenario, you would check travel times between locations here
suggested_appointments.append(Appointment(
start_time=available_start,
end_time=available_end,
professional_id=prof_id,
location="Assumed Single Location" # Placeholder
))
# Adjust start time for next potential slot by considering travel time
available_start = available_end + timedelta(minutes=30) # Assuming fixed travel time for simplification
else:
# Move to the next slot if this one is not suitable
available_start += timedelta(minutes=15) # Check every 15 minutes for availability
return suggested_appointments
"""
def test_function_visitor():
tree = ast.parse(SAMPLE_CODE)
visitor = FunctionVisitor()
visitor.visit(tree)
# Check that all Pydantic classes are identified
assert "SomeClass" not in visitor.pydantic_classes
assert "ProfessionalWorkingHours" in visitor.pydantic_classes
# Check that all functions are identified
assert len(visitor.pydantic_classes) == 4
assert len(visitor.functions) == 3
# Visiting a simple function definition with no arguments or return type
def test_simple_function_definition():
# Initialize FunctionVisitor
visitor = FunctionVisitor()
# Create AST for a simple function definition
code = ast.parse("def my_function():\n pass")
# Visit the AST
visitor.visit(code)
# Assert that the function was added to the functions dictionary
assert "my_function" in visitor.functions
# Assert the properties of the FunctionDef object
function_def = visitor.functions["my_function"]
assert function_def.name == "my_function"
assert function_def.args == ""
assert function_def.return_type == "Unknown"
assert function_def.function_template == "def my_function():\n pass"
assert function_def.function_code == "def my_function():\n pass"
# Visiting a function definition with default arguments
def test_function_with_default_arguments():
# Initialize FunctionVisitor
visitor = FunctionVisitor()
# Create AST for a function definition with default arguments
code = ast.parse("def my_function(arg1, arg2='default', arg3=123):\n pass")
# Visit the AST
visitor.visit(code)
# Assert that the function was added to the functions dictionary
assert "my_function" in visitor.functions
# Assert the properties of the FunctionDef object
function_def = visitor.functions["my_function"]
assert function_def.name == "my_function"
assert function_def.args == "Unknown, Unknown, Unknown"
assert function_def.return_type == "Unknown"
assert (
function_def.function_template
== "def my_function(arg1, arg2='default', arg3=123):\n pass"
)
assert (
function_def.function_code
== "def my_function(arg1, arg2='default', arg3=123):\n pass"
)
# Visiting a function definition with a decorator
def test_visiting_function_with_decorator():
# Initialize FunctionVisitor
visitor = FunctionVisitor()
# Create AST for a function definition with a decorator
code = ast.parse("@decorator\ndef my_function():\n pass")
# Visit the AST
visitor.visit(code)
# Assert that the function was added to the functions dictionary
assert "my_function" in visitor.functions
# Assert the properties of the FunctionDef object
function_def = visitor.functions["my_function"]
assert function_def.name == "my_function"
assert function_def.args == ""
assert function_def.return_type == "Unknown"
assert function_def.function_template == "@decorator\ndef my_function():\n pass"
assert function_def.function_code == "@decorator\ndef my_function():\n pass"
# Visiting a class definition with no Pydantic inheritance
def test_visiting_class_definition_no_pydantic_inheritance():
# Initialize FunctionVisitor
visitor = FunctionVisitor()
# Create AST for a class definition with no Pydantic inheritance
code = ast.parse("class MyClass:\n pass")
# Visit the AST
visitor.visit(code)
# Assert that the pydantic_classes list is empty
assert len(visitor.pydantic_classes) == 0
# Visiting a class definition with Pydantic inheritance
def test_visiting_class_with_pydantic_inheritance():
# Initialize FunctionVisitor
visitor = FunctionVisitor()
# Create AST for a class definition with Pydantic inheritance
code = ast.parse("class MyClass(pydantic.BaseModel):\n pass")
# Visit the AST
visitor.visit(code)
# Assert that the class name was added to the list of Pydantic classes
assert "MyClass" in visitor.pydantic_classes
# Visiting an import statement with an alias
def test_visiting_import_statement_with_alias():
# Initialize FunctionVisitor
visitor = FunctionVisitor()
# Create AST for an import statement with an alias
code = ast.parse("import module as m")
# Visit the AST
visitor.visit(code)
# Assert that the import statement was added to the imports list
assert "import module as m" in visitor.imports
# Visiting an import statement with no alias
def test_visiting_import_statement_with_no_alias():
# Initialize FunctionVisitor
visitor = FunctionVisitor()
# Create AST for an import statement with no alias
code = ast.parse("import module")
# Visit the AST
visitor.visit_Import(code.body[0])
# Assert that the import line was added to the imports list
assert visitor.imports == ["import module"]
# Visiting a function definition with annotations that are not strings or constants
def test_visiting_function_with_non_string_annotations():
# Initialize FunctionVisitor
visitor = FunctionVisitor()
# Create AST for a function definition with non-string annotations
code = ast.parse(
"def my_function(arg1: int, arg2: List[str]) -> Dict[str, int]:\n pass"
)
# Visit the AST
visitor.visit(code)
# Assert that the function was added to the functions dictionary
assert "my_function" in visitor.functions
# Assert the properties of the FunctionDef object
function_def = visitor.functions["my_function"]
assert function_def.name == "my_function"
assert function_def.args == "int, List[str]"
assert function_def.return_type == "Dict[str, int]"
assert (
function_def.function_template
== "def my_function(arg1: int, arg2: List[str]) -> Dict[str, int]:\n pass"
)
assert (
function_def.function_code
== "def my_function(arg1: int, arg2: List[str]) -> Dict[str, int]:\n pass"
)
# Visiting a function definition with a return annotation that is not a string or constant
def test_visiting_function_with_non_string_return_annotation():
# Initialize FunctionVisitor
visitor = FunctionVisitor()
# Create AST for a function definition with a non-string return annotation
code = ast.parse("def my_function() -> int:\n pass")
# Visit the AST
visitor.visit(code)
# Assert that the function was added to the functions dictionary
assert "my_function" in visitor.functions
# Assert the properties of the FunctionDef object
function_def = visitor.functions["my_function"]
assert function_def.name == "my_function"
assert function_def.args == ""
assert function_def.return_type == "int"
assert function_def.function_template == "def my_function() -> int:\n pass"
assert function_def.function_code == "def my_function() -> int:\n pass"
# Visiting a class definition with a body that is not a list of statements
def test_visiting_class_definition_with_non_list_body():
# Initialize FunctionVisitor
visitor = FunctionVisitor()
# Create AST for a class definition with a non-list body
code = ast.parse("class MyClass:\n x = 1")
# Visit the AST
visitor.visit(code)
# Assert that the class was not added to the pydantic_classes list
assert "MyClass" not in visitor.pydantic_classes
# Visiting a function definition with a body that is not a list of statements
def test_visiting_function_with_non_list_body():
# Initialize FunctionVisitor
visitor = FunctionVisitor()
# Create AST for a function definition with a non-list body
code = ast.parse("def my_function():\n print('Hello, World!')")
# Visit the AST
visitor.visit(code)
# Assert that the function was added to the functions dictionary
assert "my_function" in visitor.functions
# Assert the properties of the FunctionDef object
function_def = visitor.functions["my_function"]
assert function_def.name == "my_function"
assert function_def.args == ""
assert function_def.return_type == "Unknown"
assert function_def.function_template == "def my_function():\n pass"
assert (
function_def.function_code == "def my_function():\n print('Hello, World!')"
)
+2 -2
View File
@@ -127,8 +127,8 @@ async def test_recursive_create_code_graphs():
setup_logging(local=True)
ids = Identifiers(
user_id="123e4567-e89b-12d3-a456-426614174000",
app_id="d3954dee-6b41-47f5-be2f-a253f7544a59",
user_id=user_id_1,
app_id=app_id_1,
)
spec = await get_latest_specification(ids.user_id, ids.app_id)
ans = await agent.develop_application(ids=ids, spec=spec)