Merge pull request #1007 from Pythagora-io/lab

fixes for a few minor issues:

    fix implement_changes during iteration and fix triggering I'm stuck in a loop
    fix when we are getting relevant files and describing new files
    add iteration index to steps and fix sending stats to extension
    when sending relevant files in prompt, use both relevant and modified files
This commit is contained in:
Senko Rašić
2024-06-10 15:50:39 +02:00
committed by GitHub
10 changed files with 72 additions and 25 deletions

View File

@@ -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(

View File

@@ -67,20 +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 or []}"
)
# Check which files are relevant to the current task
if self.current_state.files and self.current_state.relevant_files is None:
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)
@@ -133,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.
@@ -172,11 +160,18 @@ 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']}")
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 self.current_state.relevant_files is None:
await self.get_relevant_files()
current_task_index = self.current_state.tasks.index(task)
llm = self.get_llm()
@@ -236,6 +231,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
@@ -248,6 +244,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}")

View File

@@ -150,7 +150,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
@@ -291,7 +299,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),

View File

@@ -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)

View File

@@ -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,

View File

@@ -168,8 +168,11 @@ class ProjectState(Base):
:return: List of tuples with file path and content.
"""
relevant = self.relevant_files or []
return [file for file in self.files if file.path in relevant]
relevant_files = self.relevant_files or []
modified_files = self.modified_files or {}
all_files = set(relevant_files + list(modified_files.keys()))
return [file for file in self.files if file.path in all_files]
@staticmethod
def create_initial_state(branch: "Branch") -> "ProjectState":
@@ -386,3 +389,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

View File

@@ -178,12 +178,15 @@ class StateManager:
)
if self.current_state.current_epic and self.current_state.current_task 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

View File

@@ -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()

View File

@@ -89,6 +89,7 @@ class PlainConsoleUI(UIBase):
source: str,
status: str,
source_index: int = 1,
tasks: list[dict] = None,
):
pass

View File

@@ -262,6 +262,7 @@ class IPCClientUI(UIBase):
source: str,
status: str,
source_index: int = 1,
tasks: list[dict] = None,
):
await self._send(
MessageType.PROGRESS,
@@ -273,7 +274,8 @@ class IPCClientUI(UIBase):
"source": source,
"status": status,
"source_index": source_index,
}
},
"all_tasks": tasks,
},
)