From 0cf42fe76899e1f806c12bf0b42f889d022e5518 Mon Sep 17 00:00:00 2001 From: LeonOstrez Date: Wed, 5 Jun 2024 08:06:26 +0100 Subject: [PATCH 1/4] fix implement_changes during iteration and fix triggering I'm stuck in a loop --- core/agents/code_monkey.py | 3 ++- core/agents/troubleshooter.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/core/agents/code_monkey.py b/core/agents/code_monkey.py index 247befa6..46ee2f6f 100644 --- a/core/agents/code_monkey.py +++ b/core/agents/code_monkey.py @@ -72,12 +72,13 @@ class CodeMonkey(BaseAgent): attempt = 1 feedback = None + iterations = self.current_state.iterations llm = self.get_llm() convo = self._get_task_convo().template( "implement_changes", file_name=file_name, file_content=file_content, - instructions=task["instructions"], + instructions=iterations[-1]["description"] if iterations else task["instructions"], ) if feedback: convo.assistant(f"```\n{self.prev_response.data['new_content']}\n```\n").template( diff --git a/core/agents/troubleshooter.py b/core/agents/troubleshooter.py index 8744f05c..5b3b5de7 100644 --- a/core/agents/troubleshooter.py +++ b/core/agents/troubleshooter.py @@ -46,7 +46,7 @@ class Troubleshooter(IterationPromptMixin, BaseAgent): # Developer sets iteration as "completed" when it generates the step breakdown, so we can't # use "current_iteration" here - last_iteration = self.current_state.iterations[-1] if self.current_state.iterations else None + last_iteration = self.current_state.iterations[-1] if len(self.current_state.iterations) >= 3 else None should_iterate, is_loop, user_feedback = await self.get_user_feedback( run_command, From d815d69782e2e7d50b67083a53e01e09970c755c Mon Sep 17 00:00:00 2001 From: LeonOstrez Date: Wed, 5 Jun 2024 08:11:59 +0100 Subject: [PATCH 2/4] fix when we are getting relevant files and describing new files --- core/agents/developer.py | 17 +++++------------ core/agents/orchestrator.py | 10 +++++++++- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/core/agents/developer.py b/core/agents/developer.py index 4b50c6ec..5ca22ef1 100644 --- a/core/agents/developer.py +++ b/core/agents/developer.py @@ -67,18 +67,6 @@ class Developer(BaseAgent): if self.prev_response and self.prev_response.type == ResponseType.TASK_REVIEW_FEEDBACK: return await self.breakdown_current_iteration(self.prev_response.data["feedback"]) - # If any of the files are missing metadata/descriptions, those need to be filled-in - missing_descriptions = [file.path for file in self.current_state.files if not file.meta.get("description")] - if missing_descriptions: - log.debug(f"Some files are missing descriptions: {', '.join(missing_descriptions)}, reqesting analysis") - return AgentResponse.describe_files(self) - - log.debug(f"Current state files: {len(self.current_state.files)}, relevant {self.current_state.relevant_files}") - # Check which files are relevant to the current task - if self.current_state.files and not self.current_state.relevant_files: - await self.get_relevant_files() - return AgentResponse.done(self) - if not self.current_state.unfinished_tasks: log.warning("No unfinished tasks found, nothing to do (why am I called? is this a bug?)") return AgentResponse.done(self) @@ -174,6 +162,11 @@ class Developer(BaseAgent): log.debug(f"Breaking down the current task: {task['description']}") await self.send_message("Thinking about how to implement this task ...") + log.debug(f"Current state files: {len(self.current_state.files)}, relevant {self.current_state.relevant_files}") + # Check which files are relevant to the current task + if self.current_state.files and not self.current_state.relevant_files: + await self.get_relevant_files() + current_task_index = self.current_state.tasks.index(task) llm = self.get_llm() diff --git a/core/agents/orchestrator.py b/core/agents/orchestrator.py index 5b59b37e..c8152b19 100644 --- a/core/agents/orchestrator.py +++ b/core/agents/orchestrator.py @@ -148,7 +148,15 @@ class Orchestrator(BaseAgent): # If there are any new or modified files changed outside Pythagora, # this is a good time to add them to the project. If any of them have # INPUT_REQUIRED, we'll first ask the user to provide the required input. - return await self.import_files() + import_files_response = await self.import_files() + + # If any of the files are missing metadata/descriptions, those need to be filled-in + missing_descriptions = [file.path for file in self.current_state.files if not file.meta.get("description")] + if missing_descriptions: + log.debug(f"Some files are missing descriptions: {', '.join(missing_descriptions)}, requesting analysis") + return AgentResponse.describe_files(self) + + return import_files_response def create_agent(self, prev_response: Optional[AgentResponse]) -> BaseAgent: state = self.current_state From c08c036d8961767359209589b2f972b8181b2fc4 Mon Sep 17 00:00:00 2001 From: LeonOstrez Date: Wed, 5 Jun 2024 14:07:28 +0100 Subject: [PATCH 3/4] add iteration index to steps and fix sending stats to extension --- core/agents/developer.py | 6 ++++++ core/agents/orchestrator.py | 2 +- core/agents/task_completer.py | 10 +++++++--- core/db/models/project_state.py | 26 ++++++++++++++++++++++++++ core/state/state_manager.py | 5 ++++- core/ui/base.py | 2 ++ core/ui/console.py | 1 + core/ui/ipc_client.py | 4 +++- 8 files changed, 50 insertions(+), 6 deletions(-) diff --git a/core/agents/developer.py b/core/agents/developer.py index 5ca22ef1..ec0cf8e2 100644 --- a/core/agents/developer.py +++ b/core/agents/developer.py @@ -119,6 +119,8 @@ class Developer(BaseAgent): self.current_state.current_task["description"], source, "in-progress", + self.current_state.get_source_index(source), + self.current_state.tasks, ) llm = self.get_llm() # FIXME: In case of iteration, parse_task depends on the context (files, tasks, etc) set there. @@ -157,6 +159,8 @@ class Developer(BaseAgent): self.current_state.current_task["description"], source, "in-progress", + self.current_state.get_source_index(source), + self.current_state.tasks, ) log.debug(f"Breaking down the current task: {task['description']}") @@ -229,6 +233,7 @@ class Developer(BaseAgent): "id": uuid4().hex, "completed": False, "source": source, + "iteration_index": len(self.current_state.iterations), **step.model_dump(), } for step in response.steps @@ -241,6 +246,7 @@ class Developer(BaseAgent): "completed": False, "type": "review_task", "source": source, + "iteration_index": len(self.current_state.iterations), }, ] log.debug(f"Next steps: {self.next_state.unfinished_steps}") diff --git a/core/agents/orchestrator.py b/core/agents/orchestrator.py index c8152b19..208e55f1 100644 --- a/core/agents/orchestrator.py +++ b/core/agents/orchestrator.py @@ -285,7 +285,7 @@ class Orchestrator(BaseAgent): async def update_stats(self): if self.current_state.steps and self.current_state.current_step: source = self.current_state.current_step.get("source") - source_steps = [s for s in self.current_state.steps if s.get("source") == source] + source_steps = self.current_state.get_last_iteration_steps() await self.ui.send_step_progress( source_steps.index(self.current_state.current_step) + 1, len(source_steps), diff --git a/core/agents/task_completer.py b/core/agents/task_completer.py index e782dcee..20ec3bf9 100644 --- a/core/agents/task_completer.py +++ b/core/agents/task_completer.py @@ -14,12 +14,16 @@ class TaskCompleter(BaseAgent): self.next_state.action = f"Task #{current_task_index1} complete" self.next_state.complete_task() await self.state_manager.log_task_completed() + tasks = self.current_state.tasks + source = self.current_state.current_epic.get("source", "app") await self.ui.send_task_progress( - self.current_state.tasks.index(self.current_state.current_task) + 1, - len(self.current_state.tasks), + tasks.index(self.current_state.current_task) + 1, + len(tasks), self.current_state.current_task["description"], - self.current_state.current_epic.get("source", "app"), + source, "done", + self.current_state.get_source_index(source), + tasks, ) return AgentResponse.done(self) diff --git a/core/db/models/project_state.py b/core/db/models/project_state.py index 0950494e..96db2570 100644 --- a/core/db/models/project_state.py +++ b/core/db/models/project_state.py @@ -380,3 +380,29 @@ class ProjectState(Base): ProjectState.step_index > self.step_index, ) ) + + def get_last_iteration_steps(self) -> list: + """ + Get the steps of the last iteration. + + :return: A list of steps. + """ + return [s for s in self.steps if s.get("iteration_index") == len(self.iterations)] or self.steps + + def get_source_index(self, source: str) -> int: + """ + Get the index of the source which can be one of: 'app', 'feature', 'troubleshooting', 'review'. For example, + for feature return value would be number of current feature. + + :param source: The source to search for. + :return: The index of the source. + """ + if source in ["app", "feature"]: + return len([epic for epic in self.epics if epic.get("source") == source]) + elif source == "troubleshooting": + return len(self.iterations) + elif source == "review": + steps = self.get_last_iteration_steps() + return len([step for step in steps if step.get("type") == "review_task"]) + + return 1 diff --git a/core/state/state_manager.py b/core/state/state_manager.py index 6c6eb3f8..a3ce779b 100644 --- a/core/state/state_manager.py +++ b/core/state/state_manager.py @@ -179,12 +179,15 @@ class StateManager: ) if self.current_state.current_epic and self.ui: + source = self.current_state.current_epic.get("source", "app") await self.ui.send_task_progress( self.current_state.tasks.index(self.current_state.current_task) + 1, len(self.current_state.tasks), self.current_state.current_task["description"], - self.current_state.current_epic.get("source", "app"), + source, "in-progress", + self.current_state.get_source_index(source), + self.current_state.tasks, ) return self.current_state diff --git a/core/ui/base.py b/core/ui/base.py index 9dd16ea6..ac586ce3 100644 --- a/core/ui/base.py +++ b/core/ui/base.py @@ -169,6 +169,7 @@ class UIBase: source: str, status: str, source_index: int = 1, + tasks: list[dict] = None, ): """ Send a task progress update to the UI. @@ -179,6 +180,7 @@ class UIBase: :param source: Source of the task, one of: 'app', 'feature', 'debugger', 'troubleshooting', 'review'. :param status: Status of the task, can be 'in_progress' or 'done'. :param source_index: Index of the source. + :param tasks: List of all tasks. """ raise NotImplementedError() diff --git a/core/ui/console.py b/core/ui/console.py index 887c75f8..d34ad81f 100644 --- a/core/ui/console.py +++ b/core/ui/console.py @@ -84,6 +84,7 @@ class PlainConsoleUI(UIBase): source: str, status: str, source_index: int = 1, + tasks: list[dict] = None, ): pass diff --git a/core/ui/ipc_client.py b/core/ui/ipc_client.py index f359413f..372d57d4 100644 --- a/core/ui/ipc_client.py +++ b/core/ui/ipc_client.py @@ -261,6 +261,7 @@ class IPCClientUI(UIBase): source: str, status: str, source_index: int = 1, + tasks: list[dict] = None, ): await self._send( MessageType.PROGRESS, @@ -272,7 +273,8 @@ class IPCClientUI(UIBase): "source": source, "status": status, "source_index": source_index, - } + }, + "all_tasks": tasks, }, ) From 26e913b31ad59fc9346ba9821f144307fb868227 Mon Sep 17 00:00:00 2001 From: LeonOstrez Date: Mon, 10 Jun 2024 11:45:26 +0100 Subject: [PATCH 4/4] when sending relevant files in prompt, use both relevant and modified files --- core/db/models/project_state.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/db/models/project_state.py b/core/db/models/project_state.py index 96db2570..304002cf 100644 --- a/core/db/models/project_state.py +++ b/core/db/models/project_state.py @@ -167,7 +167,8 @@ class ProjectState(Base): :return: List of tuples with file path and content. """ - return [file for file in self.files if file.path in self.relevant_files] + all_files = set(self.relevant_files + list(self.modified_files.keys())) + return [file for file in self.files if file.path in all_files] @staticmethod def create_initial_state(branch: "Branch") -> "ProjectState":