mirror of
https://github.com/Significant-Gravitas/AutoGPT-Code-Ability.git
synced 2026-11-10 08:01:27 -10:00
To prevent frustrated tester, we should let buggy code to be generated instead of failing the project generation (if it failed on the linter) by appending a # TODO line on the code. --------- Co-authored-by: majdyz <[email protected]>
226 lines
8.1 KiB
Python
226 lines
8.1 KiB
Python
import logging
|
|
from typing import List
|
|
|
|
from prisma.enums import DevelopmentPhase, FunctionState
|
|
from prisma.models import Function
|
|
from prisma.types import (
|
|
FunctionCreateInput,
|
|
FunctionUpdateInput,
|
|
PackageCreateWithoutRelationsInput,
|
|
)
|
|
|
|
from codex.common.ai_block import (
|
|
TODO_COMMENT,
|
|
AIBlock,
|
|
Identifiers,
|
|
ListValidationError,
|
|
ValidatedResponse,
|
|
ValidationError,
|
|
)
|
|
from codex.common.database import INCLUDE_FUNC
|
|
from codex.common.logging import log_event
|
|
from codex.common.model import create_object_type
|
|
from codex.develop.code_validation import CodeValidator
|
|
from codex.develop.function import construct_function
|
|
from codex.develop.model import GeneratedFunctionResponse, Package
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
def parse_requirements(requirements_str: str) -> List[Package]:
|
|
"""
|
|
Parses a string of requirements and creates a list of Package objects.
|
|
|
|
Args:
|
|
requirements_str (str): A string containing package requirements.
|
|
|
|
Returns:
|
|
List[Package]: A list of Package objects.
|
|
"""
|
|
logger.debug("🔍 Parsing requirements...")
|
|
packages = []
|
|
version_specifiers = ["==", ">=", "<=", ">", "<", "~=", "!="]
|
|
if requirements_str == "":
|
|
return packages
|
|
for line in requirements_str.splitlines():
|
|
if line:
|
|
# Remove comments and whitespace
|
|
line = line.split("#")[0].strip()
|
|
if not line:
|
|
continue
|
|
|
|
package_name, version, specifier = line, None, None
|
|
|
|
# Try to split by each version specifier
|
|
for spec in version_specifiers:
|
|
if spec in line:
|
|
parts = line.split(spec)
|
|
package_name = parts[0].strip()
|
|
version = parts[1].strip() if len(parts) > 1 else None
|
|
specifier = spec
|
|
break
|
|
|
|
package = Package(
|
|
package_name=package_name, version=version, specifier=specifier
|
|
)
|
|
packages.append(package)
|
|
|
|
return packages
|
|
|
|
|
|
class DevelopAIBlock(AIBlock):
|
|
developement_phase: DevelopmentPhase = DevelopmentPhase.DEVELOPMENT
|
|
prompt_template_name = "develop"
|
|
model = "gpt-4-0125-preview"
|
|
langauge = "python"
|
|
|
|
async def validate(
|
|
self, invoke_params: dict, response: ValidatedResponse
|
|
) -> ValidatedResponse:
|
|
func_name = invoke_params.get("function_name", "")
|
|
validation_errors = ListValidationError(f"Error developing `{func_name}`")
|
|
try:
|
|
text = response.response
|
|
|
|
# Package parsing
|
|
requirement_blocks = text.split("```requirements")
|
|
requirement_blocks.pop(0)
|
|
if len(requirement_blocks) < 1:
|
|
packages = []
|
|
elif len(requirement_blocks) > 1:
|
|
packages = []
|
|
validation_errors.append_message(
|
|
f"There are {len(requirement_blocks)} requirements blocks in the response. "
|
|
+ "There should be exactly 1"
|
|
)
|
|
else:
|
|
packages: List[Package] = parse_requirements(
|
|
requirement_blocks[0].split("```")[0]
|
|
)
|
|
|
|
# Code parsing
|
|
code_blocks = text.split("```python")
|
|
code_blocks.pop(0)
|
|
if len(code_blocks) == 0:
|
|
raise ValidationError("No code blocks found in the response")
|
|
elif len(code_blocks) != 1:
|
|
validation_errors.append_message(
|
|
f"There are {len(code_blocks)} code blocks in the response. "
|
|
+ "There should be exactly 1"
|
|
)
|
|
code = code_blocks[0].split("```")[0]
|
|
route_errors_as_todo = not invoke_params.get("will_retry_on_failure", True)
|
|
response.response = await CodeValidator(
|
|
compiled_route_id=invoke_params["compiled_route_id"],
|
|
database_schema=invoke_params["database_schema"],
|
|
function_name=invoke_params["function_name"],
|
|
available_objects=invoke_params["available_objects"],
|
|
available_functions=invoke_params["available_functions"],
|
|
).validate_code(
|
|
packages=packages,
|
|
raw_code=code,
|
|
route_errors_as_todo=route_errors_as_todo,
|
|
)
|
|
|
|
except ValidationError as e:
|
|
validation_errors.append_error(e)
|
|
|
|
except Exception as e:
|
|
# This is not a validation error we want to the agent to fix
|
|
# it is a code bug in the validation logic
|
|
logger.exception(e)
|
|
raise e
|
|
|
|
validation_errors.raise_if_errors()
|
|
return response
|
|
|
|
async def create_item(
|
|
self, ids: Identifiers, validated_response: ValidatedResponse
|
|
) -> Function:
|
|
"""
|
|
Update an item in the database with the given identifiers
|
|
and validated response.
|
|
|
|
Args:
|
|
ids (Identifiers): The identifiers of the item to be updated.
|
|
validated_response (ValidatedResponse): The validated response
|
|
containing the updated data.
|
|
|
|
Returns:
|
|
func: The updated item
|
|
"""
|
|
generated_response: GeneratedFunctionResponse = validated_response.response
|
|
|
|
for obj in generated_response.objects:
|
|
generated_response.available_objects = await create_object_type(
|
|
obj, generated_response.available_objects
|
|
)
|
|
|
|
function_defs: list[FunctionCreateInput] = []
|
|
if generated_response.functions:
|
|
for function in generated_response.functions:
|
|
# Skip if the function is already available
|
|
available_functions = generated_response.available_functions
|
|
if function.name in available_functions:
|
|
same_func = available_functions[function.name]
|
|
function.validate_matching_function(same_func)
|
|
continue
|
|
|
|
model = await construct_function(
|
|
function, generated_response.available_objects
|
|
)
|
|
model["CompiledRoute"] = {
|
|
"connect": {"id": generated_response.compiled_route_id}
|
|
}
|
|
function_defs.append(model)
|
|
|
|
update_obj = FunctionUpdateInput(
|
|
state=FunctionState.WRITTEN,
|
|
Packages={
|
|
"create": [
|
|
PackageCreateWithoutRelationsInput(
|
|
packageName=p.package_name,
|
|
version=p.version if p.version else "",
|
|
specifier=p.specifier if p.specifier else "",
|
|
)
|
|
for p in generated_response.packages
|
|
]
|
|
},
|
|
rawCode=generated_response.rawCode,
|
|
importStatements=generated_response.imports,
|
|
functionCode=generated_response.functionCode,
|
|
template=generated_response.template,
|
|
ChildFunctions={"create": function_defs}, # type: ignore
|
|
)
|
|
|
|
if not generated_response.function_id:
|
|
raise AssertionError("Function ID is required to update")
|
|
|
|
compiled_code = generated_response.get_compiled_code()
|
|
if TODO_COMMENT in compiled_code:
|
|
await log_event(
|
|
id=ids,
|
|
step=DevelopmentPhase.DEVELOPMENT,
|
|
event="CODE_ERRORS_AS_TODO_COMMENTS",
|
|
key=generated_response.function_id,
|
|
data=f"{generated_response.db_schema}\n#-----#\n{compiled_code}",
|
|
)
|
|
|
|
func: Function | None = await Function.prisma().update(
|
|
where={"id": generated_response.function_id},
|
|
data=update_obj,
|
|
include={
|
|
**INCLUDE_FUNC["include"],
|
|
"ParentFunction": INCLUDE_FUNC,
|
|
"ChildFunctions": INCLUDE_FUNC, # type: ignore
|
|
},
|
|
)
|
|
if not func:
|
|
raise AssertionError(
|
|
f"Function with id {generated_response.function_id} not found"
|
|
)
|
|
|
|
logger.info(f"✅ Updated Function: {func.functionName} - {func.id}")
|
|
|
|
return func
|