Skip to content

Commit

Permalink
Runnable codeplan loop (#365)
Browse files Browse the repository at this point in the history
* Add core API for codeplan

* Add maven compilation validator POC

Signed-off-by: Fabian von Feilitzsch <[email protected]>

* Plug Maven validator into the loop

Signed-off-by: Fabian von Feilitzsch <[email protected]>

---------

Signed-off-by: JonahSussman <[email protected]>
Signed-off-by: Fabian von Feilitzsch <[email protected]>
Co-authored-by: JonahSussman <[email protected]>
  • Loading branch information
2 people authored and shawn-hurley committed Sep 16, 2024
1 parent bfa1288 commit 2630632
Show file tree
Hide file tree
Showing 3 changed files with 467 additions and 0 deletions.
57 changes: 57 additions & 0 deletions playpen/repo_level_awareness/api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
from abc import ABC
from dataclasses import dataclass
from pathlib import Path
from typing import Any, Generator, Iterator, Optional

Check failure on line 4 in playpen/repo_level_awareness/api.py

View workflow job for this annotation

GitHub Actions / Trunk Check

ruff(F401)

[new] `typing.Any` imported but unused

Check failure on line 4 in playpen/repo_level_awareness/api.py

View workflow job for this annotation

GitHub Actions / Trunk Check

ruff(F401)

[new] `typing.Generator` imported but unused

Check failure on line 4 in playpen/repo_level_awareness/api.py

View workflow job for this annotation

GitHub Actions / Trunk Check

ruff(F401)

[new] `typing.Iterator` imported but unused

Check failure on line 4 in playpen/repo_level_awareness/api.py

View workflow job for this annotation

GitHub Actions / Trunk Check

ruff(F401)

[new] `typing.Optional` imported but unused

from pydantic import BaseModel

Check failure on line 6 in playpen/repo_level_awareness/api.py

View workflow job for this annotation

GitHub Actions / Trunk Check

ruff(F401)

[new] `pydantic.BaseModel` imported but unused


@dataclass
class RpcClientConfig:
repo_directory: Path


# FIXME: Oh god oh no oh jeez oh man
class Task:
pass


# FIXME: Might not need
class TaskResult:
encountered_errors: list[str]
modified_files: list[Path]


@dataclass
class ValidationError(Task):
pass


@dataclass
class ValidationResult:
passed: bool
errors: list[ValidationError]


class ValidationStep(ABC):

Check failure on line 36 in playpen/repo_level_awareness/api.py

View workflow job for this annotation

GitHub Actions / Trunk Check

ruff(B024)

[new] `ValidationStep` is an abstract base class, but it has no abstract methods
def __init__(self, RpcClientConfig: RpcClientConfig) -> None:
self.config = RpcClientConfig

def run(self) -> ValidationResult:

Check failure on line 40 in playpen/repo_level_awareness/api.py

View workflow job for this annotation

GitHub Actions / Trunk Check

ruff(B027)

[new] `ValidationStep.run` is an empty method in an abstract base class, but has no abstract decorator
pass


class Agent(ABC):

Check failure on line 44 in playpen/repo_level_awareness/api.py

View workflow job for this annotation

GitHub Actions / Trunk Check

ruff(B024)

[new] `Agent` is an abstract base class, but it has no abstract methods
def can_handle_task(self, task: Task) -> bool:

Check failure on line 45 in playpen/repo_level_awareness/api.py

View workflow job for this annotation

GitHub Actions / Trunk Check

ruff(B027)

[new] `Agent.can_handle_task` is an empty method in an abstract base class, but has no abstract decorator
pass

def execute_task(self, task: Task) -> TaskResult:

Check failure on line 48 in playpen/repo_level_awareness/api.py

View workflow job for this annotation

GitHub Actions / Trunk Check

ruff(B027)

[new] `Agent.execute_task` is an empty method in an abstract base class, but has no abstract decorator
pass

def refine_task(self, errors: list[str]) -> None:

Check failure on line 51 in playpen/repo_level_awareness/api.py

View workflow job for this annotation

GitHub Actions / Trunk Check

ruff(B027)

[new] `Agent.refine_task` is an empty method in an abstract base class, but has no abstract decorator
# Knows that it's the refine step so that it might not spawn as much
# stuff.
pass

def can_handle_error(self, errors: list[str]) -> bool:

Check failure on line 56 in playpen/repo_level_awareness/api.py

View workflow job for this annotation

GitHub Actions / Trunk Check

ruff(B027)

[new] `Agent.can_handle_error` is an empty method in an abstract base class, but has no abstract decorator
pass
157 changes: 157 additions & 0 deletions playpen/repo_level_awareness/codeplan.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
#!/usr/bin/env python

Check failure on line 1 in playpen/repo_level_awareness/codeplan.py

View workflow job for this annotation

GitHub Actions / Trunk Check

isort

Incorrect formatting, autoformat by running 'trunk fmt'

from typing import Any, Generator, List, Optional, Type

Check failure on line 3 in playpen/repo_level_awareness/codeplan.py

View workflow job for this annotation

GitHub Actions / Trunk Check

ruff(F401)

[new] `typing.List` imported but unused

Check failure on line 3 in playpen/repo_level_awareness/codeplan.py

View workflow job for this annotation

GitHub Actions / Trunk Check

ruff(F401)

[new] `typing.Type` imported but unused

from kai.service.kai_application.kai_application import UpdatedFileContent

from api import Agent, RpcClientConfig, Task, TaskResult, ValidationStep
from maven_validator import MavenCompileStep


def main():
import argparse

parser = argparse.ArgumentParser(
description="Run the CodePlan loop against a project"
)
parser.add_argument(
"source_directory", help="The root directory of the project to be fixed"
)

args = parser.parse_args()

config = RpcClientConfig(args.source_directory)
codeplan(config, None)


def codeplan(
config: RpcClientConfig,
updated_file_content: UpdatedFileContent,
):
whatever_agent = Agent()

task_manager = TaskManager(
config,
updated_file_content,
validators=[MavenCompileStep(config)],
agents=[whatever_agent],
)
# has a list of files affected and unprocessed
# has a list of registered validators
# has a list of current validation errors

for task in task_manager.get_next_task():
task_manager.supply_result(task_manager.execute_task(task))
# all current failing validations and all currently affected AND UNDEALT
# WITH files

# Can do revalidation, or use cached results or whatever


class TaskManager:
def __init__(
self,
config: RpcClientConfig,
updated_file_content: UpdatedFileContent,
validators: Optional[list[ValidationStep]] = None,
agents: Optional[list[Agent]] = None,
) -> None:

# TODO: Files maybe could become unprocessed again, but that could lead
# to infinite looping really hard, so we're avoiding it for now. Could
# even have like a MAX_DEPTH or something.
self.processed_files: list[Path] = []

Check failure on line 63 in playpen/repo_level_awareness/codeplan.py

View workflow job for this annotation

GitHub Actions / Trunk Check

ruff(F821)

[new] Undefined name `Path`
self.unprocessed_files: list[Path] = []

Check failure on line 64 in playpen/repo_level_awareness/codeplan.py

View workflow job for this annotation

GitHub Actions / Trunk Check

ruff(F821)

[new] Undefined name `Path`

self.validators: list[ValidationStep] = []
if validators is not None:
self.validators.extend(validators)

self.agents: list[Agent] = []
if agents is not None:
self.agents.extend(agents)

self.config = config

self._validators_are_stale = True

# TODO: Modify the inputs to this class accordingly
# We want all the context that went in and the result that came out too
# updated_file_content.

# TODO: Actually add the paths to processed and unprocessed files.

def execute_task(self, task: Task) -> TaskResult:
return self.get_agent_for_task(task).execute_task(task)

def get_agent_for_task(self, task: Task) -> Agent:
for agent in self.agents:
if agent.can_handle_task(task):
return agent

raise Exception("No agent available for this task")

def supply_result(self, result: TaskResult) -> None:
# One result is the filesystem changes
# SUCCESS
# - Did something, modified file system -> Recompute
# - Did nothing -> Go to next task

# another is that the agent failed
# FAILURE
# - Did it give us more info to feed back to the repo context
# - It failed and gave us nothing -> >:(

for file_path in result.modified_files:
if file_path not in self.unprocessed_files:
self.unprocessed_files.append(file_path)
self._validators_are_stale = True

if len(result.encountered_errors) > 0:
raise NotImplementedError("What should we do with errors?")

def run_validators(self) -> list[tuple[type, str]]:
# NOTE: Do it this way so that in the future we could do something
# like get all the errors in an affected file and then send THAT as
# a task, versus locking us into, one validation error per task at a
# time. i.e. Make it soe wae can combine validation errors into
# single tasks. Or grabbing all errors of a type, or all errors
# referencing a specific type.
#
# Basically, we're surfacing this functionality cause this whole.
# process is going to get more complicated in the future.
validation_errors: list[tuple[type, str]] = []

for validator in self.validators:
result = validator.run()
if not result.passed:
validation_errors.extend((type(validator), e) for e in result.errors)

self._validators_are_stale = False

return validation_errors

def get_next_task(self) -> Generator[Task, Any, None]:
validation_errors: list[tuple[type, str]] = []

# Check to see if validators are stale. If so, run them
while True:
if self._validators_are_stale:
validation_errors = self.run_validators()

# pop an error of the stack of errors
if len(validation_errors) > 0:
err = validation_errors.pop(0)
yield err # TODO: This is a placeholder
continue

if len(self.unprocessed_files) > 0:
yield Task(self.unprocessed_files.pop(0))
continue

break


if __name__ == "__main__":
with __import__("ipdb").launch_ipdb_on_exception():
main()
Loading

0 comments on commit 2630632

Please sign in to comment.