mirror of
https://github.com/Pythagora-io/gpt-pilot.git
synced 2026-01-10 13:37:55 -05:00
Implemented the first step in deep focus initiative. Now, when the user says there is a bug during the testing step, GPT Pilot adds logs before attempting to fix the issue
This commit is contained in:
190
core/agents/bug_hunter.py
Normal file
190
core/agents/bug_hunter.py
Normal file
@@ -0,0 +1,190 @@
|
||||
from enum import Enum
|
||||
from typing import Annotated, Literal, Union
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from core.agents.base import BaseAgent
|
||||
from core.agents.convo import AgentConvo
|
||||
from core.agents.response import AgentResponse
|
||||
from core.config import magic_words
|
||||
from core.db.models.project_state import IterationStatus
|
||||
from core.llm.parser import JSONParser
|
||||
from core.log import get_logger
|
||||
|
||||
log = get_logger(__name__)
|
||||
|
||||
|
||||
class StepType(str, Enum):
|
||||
ADD_LOG = "add_log"
|
||||
EXPLAIN_PROBLEM = "explain_problem"
|
||||
GET_ADDITIONAL_FILES = "get_additional_files"
|
||||
|
||||
|
||||
class Log(BaseModel):
|
||||
filePath: str
|
||||
referenceCodeSnippet: str = Field(description="Five lines of code before the line where the log needs to be added. Make sure that this contains **ONLY** the code that is currently written in the file. It must not contain the log that you want to add.")
|
||||
log: str
|
||||
|
||||
|
||||
class AddLog(BaseModel):
|
||||
type: Literal[StepType.ADD_LOG] = StepType.ADD_LOG
|
||||
logsToAdd: list[Log]
|
||||
|
||||
|
||||
class ExplainProblem(BaseModel):
|
||||
type: Literal[StepType.EXPLAIN_PROBLEM] = StepType.EXPLAIN_PROBLEM
|
||||
problem_explanation: str
|
||||
|
||||
|
||||
class GetAdditionalFiles(BaseModel):
|
||||
type: Literal[StepType.GET_ADDITIONAL_FILES] = StepType.GET_ADDITIONAL_FILES
|
||||
filePath: str
|
||||
|
||||
|
||||
# TODO enable LLM to ask for more files
|
||||
class LoggingOptions(BaseModel):
|
||||
decision: Annotated[
|
||||
Union[AddLog, ExplainProblem, GetAdditionalFiles],
|
||||
Field(discriminator="type"),
|
||||
]
|
||||
|
||||
|
||||
class HuntConclusionType(str, Enum):
|
||||
ADD_LOGS = magic_words.ADD_LOGS
|
||||
PROBLEM_IDENTIFIED = magic_words.PROBLEM_IDENTIFIED
|
||||
|
||||
|
||||
class HuntConclusionOptions(BaseModel):
|
||||
conclusion: HuntConclusionType = Field(description=f"If more logs are needed to identify the problem, respond with '{magic_words.ADD_LOGS}'. If the problem is identified, respond with '{magic_words.PROBLEM_IDENTIFIED}'.")
|
||||
|
||||
|
||||
class BugHunter(BaseAgent):
|
||||
agent_type = "bug-hunter"
|
||||
display_name = "Bug Hunter"
|
||||
|
||||
async def run(self) -> AgentResponse:
|
||||
current_iteration = self.current_state.current_iteration
|
||||
|
||||
if "bug_reproduction_description" not in current_iteration:
|
||||
await self.get_bug_reproduction_instructions()
|
||||
if current_iteration["status"] == IterationStatus.HUNTING_FOR_BUG:
|
||||
# TODO determine how to find a bug (eg. check in db, ask user a question, etc.)
|
||||
return await self.check_logs()
|
||||
elif current_iteration["status"] == IterationStatus.AWAITING_USER_TEST:
|
||||
return await self.ask_user_to_test()
|
||||
elif current_iteration["status"] == IterationStatus.AWAITING_BUG_REPRODUCTION:
|
||||
return await self.ask_user_to_test()
|
||||
|
||||
async def get_bug_reproduction_instructions(self):
|
||||
llm = self.get_llm()
|
||||
convo = (
|
||||
AgentConvo(self)
|
||||
.template(
|
||||
"get_bug_reproduction_instructions",
|
||||
current_task=self.current_state.current_task,
|
||||
user_feedback=self.current_state.current_iteration["user_feedback"],
|
||||
user_feedback_qa=self.current_state.current_iteration["user_feedback_qa"],
|
||||
docs=self.current_state.docs,
|
||||
next_solution_to_try=None,
|
||||
)
|
||||
)
|
||||
bug_reproduction_instructions = await llm(convo, temperature=0)
|
||||
self.next_state.current_iteration["bug_reproduction_description"] = bug_reproduction_instructions
|
||||
|
||||
async def check_logs(self, logs_message: str = None):
|
||||
llm = self.get_llm()
|
||||
convo = (
|
||||
AgentConvo(self)
|
||||
.template(
|
||||
"iteration",
|
||||
current_task=self.current_state.current_task,
|
||||
user_feedback=self.current_state.current_iteration["user_feedback"],
|
||||
user_feedback_qa=self.current_state.current_iteration["user_feedback_qa"],
|
||||
docs=self.current_state.docs,
|
||||
magic_words=magic_words,
|
||||
next_solution_to_try=None
|
||||
)
|
||||
)
|
||||
|
||||
for hunting_cycle in self.current_state.current_iteration["bug_hunting_cycles"]:
|
||||
convo = (convo
|
||||
.assistant(hunting_cycle["human_readable_instructions"])
|
||||
.template(
|
||||
"log_data",
|
||||
backend_logs=hunting_cycle["backend_logs"],
|
||||
frontend_logs=hunting_cycle["frontend_logs"],
|
||||
fix_attempted=hunting_cycle["fix_attempted"]
|
||||
))
|
||||
|
||||
human_readable_instructions = await llm(convo, temperature=0.5)
|
||||
|
||||
convo = (
|
||||
AgentConvo(self)
|
||||
.template(
|
||||
"bug_found_or_add_logs",
|
||||
hunt_conclusion=human_readable_instructions,
|
||||
)
|
||||
.require_schema(HuntConclusionOptions)
|
||||
)
|
||||
hunt_conclusion = await llm(convo, parser=JSONParser(HuntConclusionOptions), temperature=0)
|
||||
|
||||
self.next_state.current_iteration["description"] = human_readable_instructions
|
||||
self.next_state.current_iteration["bug_hunting_cycles"] += [{
|
||||
"human_readable_instructions": human_readable_instructions,
|
||||
"fix_attempted": False
|
||||
}]
|
||||
|
||||
if False and hunt_conclusion.conclusion == magic_words.PROBLEM_IDENTIFIED:
|
||||
# if no need for logs, implement iteration same as before
|
||||
self.next_state.current_iteration["status"] = IterationStatus.AWAITING_BUG_FIX
|
||||
await self.send_message("The bug is found - I'm attempting to fix it.")
|
||||
else:
|
||||
# if logs are needed, add logging steps
|
||||
self.next_state.current_iteration["status"] = IterationStatus.AWAITING_LOGGING
|
||||
await self.send_message("Adding more logs to identify the bug.")
|
||||
|
||||
self.next_state.flag_iterations_as_modified()
|
||||
return AgentResponse.done(self)
|
||||
|
||||
async def ask_user_to_test(self):
|
||||
|
||||
reproduce_bug_and_get_logs = self.current_state.current_iteration["status"] == IterationStatus.AWAITING_BUG_REPRODUCTION
|
||||
|
||||
await self.send_message("You can reproduce the bug like this:\n\n" + self.current_state.current_iteration["bug_reproduction_description"])
|
||||
if self.current_state.current_iteration["status"] == IterationStatus.AWAITING_USER_TEST:
|
||||
user_feedback = await self.ask_question(
|
||||
"Is the bug you reported fixed now?",
|
||||
buttons={"yes": "Yes, the issue is fixed", "no": "No"},
|
||||
default="continue",
|
||||
buttons_only=True,
|
||||
hint="Instructions for testing:\n\n" + self.current_state.current_iteration["bug_reproduction_description"]
|
||||
)
|
||||
self.next_state.current_iteration["bug_hunting_cycles"][-1]["fix_attempted"] = True
|
||||
|
||||
if user_feedback.button == "yes":
|
||||
self.next_state.complete_iteration()
|
||||
else:
|
||||
reproduce_bug_and_get_logs = True
|
||||
|
||||
if reproduce_bug_and_get_logs:
|
||||
# TODO how can we get FE and BE logs automatically?
|
||||
backend_logs = await self.ask_question(
|
||||
"Please do exactly what you did in the last iteration, paste **BACKEND** logs here and click CONTINUE.",
|
||||
buttons={"continue": "Continue"},
|
||||
default="continue",
|
||||
hint="Instructions for testing:\n\n" + self.current_state.current_iteration["bug_reproduction_description"]
|
||||
)
|
||||
|
||||
frontend_logs = await self.ask_question(
|
||||
"Please paste **frontend** logs here and click CONTINUE.",
|
||||
buttons={"continue": "Continue"},
|
||||
default="continue",
|
||||
hint="Instructions for testing:\n\n" + self.current_state.current_iteration["bug_reproduction_description"]
|
||||
)
|
||||
|
||||
# TODO select only the logs that are new (with PYTHAGORA_DEBUGGING_LOG)
|
||||
self.next_state.current_iteration["bug_hunting_cycles"][-1]["backend_logs"] = backend_logs.text
|
||||
self.next_state.current_iteration["bug_hunting_cycles"][-1]["frontend_logs"] = frontend_logs.text
|
||||
self.next_state.current_iteration["status"] = IterationStatus.HUNTING_FOR_BUG
|
||||
|
||||
return AgentResponse.done(self)
|
||||
@@ -1,13 +1,13 @@
|
||||
from enum import Enum
|
||||
from typing import Annotated, Literal, Optional, Union
|
||||
from typing import Optional
|
||||
from uuid import uuid4
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from core.agents.base import BaseAgent
|
||||
from core.agents.convo import AgentConvo
|
||||
from core.agents.mixins import TaskSteps
|
||||
from core.agents.response import AgentResponse, ResponseType
|
||||
from core.db.models.project_state import TaskStatus
|
||||
from core.db.models.project_state import IterationStatus, TaskStatus
|
||||
from core.db.models.specification import Complexity
|
||||
from core.llm.parser import JSONParser
|
||||
from core.log import get_logger
|
||||
@@ -16,47 +16,6 @@ from core.telemetry import telemetry
|
||||
log = get_logger(__name__)
|
||||
|
||||
|
||||
class StepType(str, Enum):
|
||||
COMMAND = "command"
|
||||
SAVE_FILE = "save_file"
|
||||
HUMAN_INTERVENTION = "human_intervention"
|
||||
|
||||
|
||||
class CommandOptions(BaseModel):
|
||||
command: str = Field(description="Command to run")
|
||||
timeout: int = Field(description="Timeout in seconds")
|
||||
success_message: str = ""
|
||||
|
||||
|
||||
class SaveFileOptions(BaseModel):
|
||||
path: str
|
||||
|
||||
|
||||
class SaveFileStep(BaseModel):
|
||||
type: Literal[StepType.SAVE_FILE] = StepType.SAVE_FILE
|
||||
save_file: SaveFileOptions
|
||||
|
||||
|
||||
class CommandStep(BaseModel):
|
||||
type: Literal[StepType.COMMAND] = StepType.COMMAND
|
||||
command: CommandOptions
|
||||
|
||||
|
||||
class HumanInterventionStep(BaseModel):
|
||||
type: Literal[StepType.HUMAN_INTERVENTION] = StepType.HUMAN_INTERVENTION
|
||||
human_intervention_description: str
|
||||
|
||||
|
||||
Step = Annotated[
|
||||
Union[SaveFileStep, CommandStep, HumanInterventionStep],
|
||||
Field(discriminator="type"),
|
||||
]
|
||||
|
||||
|
||||
class TaskSteps(BaseModel):
|
||||
steps: list[Step]
|
||||
|
||||
|
||||
class RelevantFiles(BaseModel):
|
||||
relevant_files: list[str] = Field(description="List of relevant files for the current task.")
|
||||
|
||||
@@ -109,6 +68,17 @@ class Developer(BaseAgent):
|
||||
n_tasks = 1
|
||||
log.debug(f"Breaking down the task review feedback {task_review_feedback}")
|
||||
await self.send_message("Breaking down the task review feedback...")
|
||||
elif (self.current_state.current_iteration["status"] == IterationStatus.AWAITING_BUG_FIX or
|
||||
self.current_state.current_iteration["status"] == IterationStatus.AWAITING_LOGGING):
|
||||
iteration = self.current_state.current_iteration
|
||||
current_task["task_review_feedback"] = None
|
||||
|
||||
description = iteration["bug_hunting_cycles"][-1]["human_readable_instructions"]
|
||||
user_feedback = iteration["user_feedback"]
|
||||
source = "bug_hunt"
|
||||
n_tasks = len(self.next_state.iterations)
|
||||
log.debug(f"Breaking down the logging cycle {description}")
|
||||
await self.send_message("Breaking down the current iteration logging cycle ...")
|
||||
else:
|
||||
iteration = self.current_state.current_iteration
|
||||
current_task["task_review_feedback"] = None
|
||||
@@ -156,8 +126,14 @@ class Developer(BaseAgent):
|
||||
self.set_next_steps(response, source)
|
||||
|
||||
if iteration:
|
||||
self.next_state.complete_iteration()
|
||||
self.next_state.action = f"Troubleshooting #{len(self.current_state.iterations)}"
|
||||
# fixme please :cry:
|
||||
if ("status" in iteration) and (iteration["status"] == IterationStatus.AWAITING_BUG_FIX or
|
||||
iteration["status"] == IterationStatus.AWAITING_LOGGING):
|
||||
self.next_state.current_iteration["status"] = IterationStatus.AWAITING_BUG_REPRODUCTION if (
|
||||
iteration["status"] == IterationStatus.AWAITING_LOGGING) else IterationStatus.AWAITING_USER_TEST
|
||||
else:
|
||||
self.next_state.complete_iteration()
|
||||
self.next_state.action = f"Troubleshooting #{len(self.current_state.iterations)}"
|
||||
else:
|
||||
self.next_state.action = "Task review feedback"
|
||||
|
||||
@@ -265,7 +241,9 @@ class Developer(BaseAgent):
|
||||
}
|
||||
for step in response.steps
|
||||
]
|
||||
if len(self.next_state.unfinished_steps) > 0 and source != "review":
|
||||
if (len(self.next_state.unfinished_steps) > 0 and
|
||||
source != "review" and
|
||||
self.next_state.current_iteration["status"] != IterationStatus.AWAITING_LOGGING):
|
||||
self.next_state.steps += [
|
||||
# TODO: add refactor step here once we have the refactor agent
|
||||
{
|
||||
|
||||
@@ -3,6 +3,7 @@ from uuid import uuid4
|
||||
from core.agents.base import BaseAgent
|
||||
from core.agents.convo import AgentConvo
|
||||
from core.agents.response import AgentResponse
|
||||
from core.db.models.project_state import IterationStatus
|
||||
from core.log import get_logger
|
||||
|
||||
log = get_logger(__name__)
|
||||
@@ -110,7 +111,7 @@ class ErrorHandler(BaseAgent):
|
||||
"description": llm_response,
|
||||
"alternative_solutions": [],
|
||||
"attempts": 1,
|
||||
"completed": False,
|
||||
"status": IterationStatus.HUNTING_FOR_BUG,
|
||||
}
|
||||
]
|
||||
# TODO: maybe have ProjectState.finished_steps as well? would make the debug/ran_command prompts nicer too
|
||||
|
||||
@@ -1,8 +1,52 @@
|
||||
from typing import Optional
|
||||
from enum import Enum
|
||||
from typing import Annotated, Literal, Optional, Union
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from core.agents.convo import AgentConvo
|
||||
|
||||
|
||||
class StepType(str, Enum):
|
||||
COMMAND = "command"
|
||||
SAVE_FILE = "save_file"
|
||||
HUMAN_INTERVENTION = "human_intervention"
|
||||
|
||||
|
||||
class CommandOptions(BaseModel):
|
||||
command: str = Field(description="Command to run")
|
||||
timeout: int = Field(description="Timeout in seconds")
|
||||
success_message: str = ""
|
||||
|
||||
|
||||
class SaveFileOptions(BaseModel):
|
||||
path: str
|
||||
|
||||
|
||||
class SaveFileStep(BaseModel):
|
||||
type: Literal[StepType.SAVE_FILE] = StepType.SAVE_FILE
|
||||
save_file: SaveFileOptions
|
||||
|
||||
|
||||
class CommandStep(BaseModel):
|
||||
type: Literal[StepType.COMMAND] = StepType.COMMAND
|
||||
command: CommandOptions
|
||||
|
||||
|
||||
class HumanInterventionStep(BaseModel):
|
||||
type: Literal[StepType.HUMAN_INTERVENTION] = StepType.HUMAN_INTERVENTION
|
||||
human_intervention_description: str
|
||||
|
||||
|
||||
Step = Annotated[
|
||||
Union[SaveFileStep, CommandStep, HumanInterventionStep],
|
||||
Field(discriminator="type"),
|
||||
]
|
||||
|
||||
|
||||
class TaskSteps(BaseModel):
|
||||
steps: list[Step]
|
||||
|
||||
|
||||
class IterationPromptMixin:
|
||||
"""
|
||||
Provides a method to find a solution to a problem based on user feedback.
|
||||
@@ -16,6 +60,7 @@ class IterationPromptMixin:
|
||||
*,
|
||||
user_feedback_qa: Optional[list[str]] = None,
|
||||
next_solution_to_try: Optional[str] = None,
|
||||
bug_hunting_cycles: Optional[dict] = None,
|
||||
) -> str:
|
||||
"""
|
||||
Generate a new solution for the problem the user reported.
|
||||
@@ -23,6 +68,7 @@ class IterationPromptMixin:
|
||||
:param user_feedback: User feedback about the problem.
|
||||
:param user_feedback_qa: Additional q/a about the problem provided by the user (optional).
|
||||
:param next_solution_to_try: Hint from ProblemSolver on which solution to try (optional).
|
||||
:param bug_hunting_cycles: Data about logs that need to be added to the code (optional).
|
||||
:return: The generated solution to the problem.
|
||||
"""
|
||||
llm = self.get_llm()
|
||||
@@ -32,6 +78,7 @@ class IterationPromptMixin:
|
||||
user_feedback=user_feedback,
|
||||
user_feedback_qa=user_feedback_qa,
|
||||
next_solution_to_try=next_solution_to_try,
|
||||
bug_hunting_cycles=bug_hunting_cycles,
|
||||
)
|
||||
llm_solution: str = await llm(convo)
|
||||
return llm_solution
|
||||
|
||||
@@ -2,6 +2,7 @@ from typing import Optional
|
||||
|
||||
from core.agents.architect import Architect
|
||||
from core.agents.base import BaseAgent
|
||||
from core.agents.bug_hunter import BugHunter
|
||||
from core.agents.code_monkey import CodeMonkey
|
||||
from core.agents.code_reviewer import CodeReviewer
|
||||
from core.agents.developer import Developer
|
||||
@@ -18,7 +19,7 @@ from core.agents.task_reviewer import TaskReviewer
|
||||
from core.agents.tech_lead import TechLead
|
||||
from core.agents.tech_writer import TechnicalWriter
|
||||
from core.agents.troubleshooter import Troubleshooter
|
||||
from core.db.models.project_state import TaskStatus
|
||||
from core.db.models.project_state import IterationStatus, TaskStatus
|
||||
from core.log import get_logger
|
||||
from core.telemetry import telemetry
|
||||
from core.ui.base import ProjectStage
|
||||
@@ -226,12 +227,25 @@ class Orchestrator(BaseAgent):
|
||||
return self.create_agent_for_step(state.current_step)
|
||||
|
||||
if state.unfinished_iterations:
|
||||
if state.current_iteration["description"]:
|
||||
# Break down the next iteration into steps
|
||||
if state.current_iteration["status"] == IterationStatus.HUNTING_FOR_BUG:
|
||||
# Ask the Logger to check if more logs in the code are needed
|
||||
return BugHunter(self.state_manager, self.ui)
|
||||
elif (state.current_iteration["status"] == IterationStatus.AWAITING_LOGGING or
|
||||
state.current_iteration["status"] == IterationStatus.AWAITING_BUG_FIX):
|
||||
# Ask the Logger to ask user to test new logs
|
||||
return Developer(self.state_manager, self.ui)
|
||||
else:
|
||||
# We need to iterate over the current task but there's no solution, as Pythagora
|
||||
# is stuck in a loop, and ProblemSolver needs to find alternative solutions.
|
||||
elif (state.current_iteration["status"] == IterationStatus.AWAITING_USER_TEST or
|
||||
state.current_iteration["status"] == IterationStatus.AWAITING_BUG_REPRODUCTION):
|
||||
# Ask the Logger to ask user to test new logs
|
||||
return BugHunter(self.state_manager, self.ui)
|
||||
elif state.current_iteration["status"] == IterationStatus.FIND_SOLUTION:
|
||||
# Find solution to the iteration problem
|
||||
return Troubleshooter(self.state_manager, self.ui)
|
||||
# elif state.current_iteration["status"] == IterationStatus.AWAITING_BUG_FIX:
|
||||
# # Break down the next iteration into steps
|
||||
# return Developer(self.state_manager, self.ui)
|
||||
elif state.current_iteration["status"] == IterationStatus.PROBLEM_SOLVER:
|
||||
# Call Problem Solver if the user said "I'm stuck in a loop"
|
||||
return ProblemSolver(self.state_manager, self.ui)
|
||||
|
||||
# We have just finished the task, call Troubleshooter to ask the user to review
|
||||
|
||||
@@ -6,6 +6,7 @@ from core.agents.base import BaseAgent
|
||||
from core.agents.convo import AgentConvo
|
||||
from core.agents.response import AgentResponse
|
||||
from core.agents.troubleshooter import IterationPromptMixin
|
||||
from core.db.models.project_state import IterationStatus
|
||||
from core.llm.parser import JSONParser
|
||||
from core.log import get_logger
|
||||
|
||||
@@ -98,6 +99,7 @@ class ProblemSolver(IterationPromptMixin, BaseAgent):
|
||||
self.next_state_iteration["alternative_solutions"][index]["tried"] = True
|
||||
self.next_state_iteration["description"] = llm_solution
|
||||
self.next_state_iteration["attempts"] = self.iteration["attempts"] + 1
|
||||
self.next_state_iteration["status"] = IterationStatus.AWAITING_BUG_FIX
|
||||
self.next_state.flag_iterations_as_modified()
|
||||
return AgentResponse.done(self)
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ from core.agents.mixins import IterationPromptMixin
|
||||
from core.agents.response import AgentResponse
|
||||
from core.config import ROUTE_FILES_AGENT_NAME
|
||||
from core.db.models.file import File
|
||||
from core.db.models.project_state import TaskStatus
|
||||
from core.db.models.project_state import IterationStatus, TaskStatus
|
||||
from core.llm.parser import JSONParser, OptionalCodeBlockParser
|
||||
from core.log import get_logger
|
||||
from core.telemetry import telemetry
|
||||
@@ -33,7 +33,29 @@ class Troubleshooter(IterationPromptMixin, BaseAgent):
|
||||
agent_type = "troubleshooter"
|
||||
display_name = "Troubleshooter"
|
||||
|
||||
async def run(self) -> AgentResponse:
|
||||
async def run(self):
|
||||
if self.current_state.unfinished_iterations:
|
||||
if self.current_state.current_iteration.get("status") == IterationStatus.FIND_SOLUTION:
|
||||
return await self.propose_solution()
|
||||
else:
|
||||
raise ValueError("There is unfinished iteration but it's not in FIND_SOLUTION state.")
|
||||
else:
|
||||
return await self.create_iteration()
|
||||
|
||||
async def propose_solution(self) -> AgentResponse:
|
||||
user_feedback = self.current_state.current_iteration.get("user_feedback")
|
||||
user_feedback_qa = self.current_state.current_iteration.get("user_feedback_qa")
|
||||
bug_hunting_cycles = self.current_state.current_iteration.get("bug_hunting_cycles")
|
||||
|
||||
llm_solution = await self.find_solution(user_feedback, user_feedback_qa=user_feedback_qa, bug_hunting_cycles=bug_hunting_cycles)
|
||||
|
||||
self.next_state.current_iteration["description"] = llm_solution
|
||||
self.next_state.current_iteration["status"] = IterationStatus.AWAITING_BUG_FIX
|
||||
self.next_state.flag_iterations_as_modified()
|
||||
|
||||
return AgentResponse.done(self)
|
||||
|
||||
async def create_iteration(self) -> AgentResponse:
|
||||
run_command = await self.get_run_command()
|
||||
|
||||
user_instructions = self.current_state.current_task.get("test_instructions")
|
||||
@@ -54,7 +76,7 @@ class Troubleshooter(IterationPromptMixin, BaseAgent):
|
||||
# use "current_iteration" here
|
||||
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(
|
||||
should_iterate, is_loop, bug_report, change_description = await self.get_user_feedback(
|
||||
run_command,
|
||||
user_instructions,
|
||||
last_iteration is not None,
|
||||
@@ -63,6 +85,7 @@ class Troubleshooter(IterationPromptMixin, BaseAgent):
|
||||
# User tested and reported no problems, we're done with the task
|
||||
return await self.complete_task()
|
||||
|
||||
user_feedback = bug_report or change_description
|
||||
user_feedback_qa = await self.generate_bug_report(run_command, user_instructions, user_feedback)
|
||||
|
||||
if is_loop:
|
||||
@@ -71,24 +94,30 @@ class Troubleshooter(IterationPromptMixin, BaseAgent):
|
||||
return self.try_next_alternative_solution(user_feedback, user_feedback_qa)
|
||||
else:
|
||||
# Newly detected loop, set up an empty new iteration to trigger ProblemSolver
|
||||
llm_solution = ""
|
||||
iteration_status = IterationStatus.AWAITING_BUG_FIX
|
||||
await self.trace_loop("loop-feedback")
|
||||
else:
|
||||
llm_solution = await self.find_solution(user_feedback, user_feedback_qa=user_feedback_qa)
|
||||
elif bug_report is not None:
|
||||
iteration_status = IterationStatus.HUNTING_FOR_BUG
|
||||
elif change_description is not None:
|
||||
iteration_status = IterationStatus.FIND_SOLUTION
|
||||
|
||||
|
||||
self.next_state.iterations = self.current_state.iterations + [
|
||||
{
|
||||
"id": uuid4().hex,
|
||||
"user_feedback": user_feedback,
|
||||
"user_feedback_qa": user_feedback_qa,
|
||||
"description": llm_solution,
|
||||
"description": change_description,
|
||||
"alternative_solutions": [],
|
||||
# FIXME - this is incorrect if this is a new problem; otherwise we could
|
||||
# just count the iterations
|
||||
"attempts": 1,
|
||||
"completed": False,
|
||||
"status": iteration_status,
|
||||
"bug_hunting_cycles": [],
|
||||
}
|
||||
]
|
||||
|
||||
self.next_state.flag_iterations_as_modified()
|
||||
if len(self.next_state.iterations) == LOOP_THRESHOLD:
|
||||
await self.trace_loop("loop-start")
|
||||
|
||||
@@ -187,6 +216,11 @@ class Troubleshooter(IterationPromptMixin, BaseAgent):
|
||||
feedback (eg. if they just clicked on "Continue" or "I'm stuck in a loop").
|
||||
"""
|
||||
|
||||
bug_report = None
|
||||
change_description = None
|
||||
is_loop = False
|
||||
should_iterate = True
|
||||
|
||||
test_message = "Can you check if the app works please?"
|
||||
if user_instructions:
|
||||
hint = " Here is a description of what should be working:\n\n" + user_instructions
|
||||
@@ -194,15 +228,19 @@ class Troubleshooter(IterationPromptMixin, BaseAgent):
|
||||
if run_command:
|
||||
await self.ui.send_run_command(run_command)
|
||||
|
||||
buttons = {"continue": "continue"}
|
||||
buttons = {
|
||||
"continue": "Everything works",
|
||||
"change": "I want to make a change",
|
||||
"bug": "There is an issue"
|
||||
}
|
||||
if last_iteration:
|
||||
buttons["loop"] = "I'm stuck in a loop"
|
||||
|
||||
user_response = await self.ask_question(test_message, buttons=buttons, default="continue", hint=hint)
|
||||
user_response = await self.ask_question(test_message, buttons=buttons, default="continue", buttons_only=True, hint=hint)
|
||||
if user_response.button == "continue" or user_response.cancelled:
|
||||
return False, False, ""
|
||||
should_iterate = False
|
||||
|
||||
if user_response.button == "loop":
|
||||
elif user_response.button == "loop":
|
||||
await telemetry.trace_code_event(
|
||||
"stuck-in-loop",
|
||||
{
|
||||
@@ -218,16 +256,23 @@ class Troubleshooter(IterationPromptMixin, BaseAgent):
|
||||
},
|
||||
},
|
||||
)
|
||||
return True, True, ""
|
||||
is_loop = True
|
||||
|
||||
return True, False, user_response.text
|
||||
elif user_response.button == "change":
|
||||
user_description = await self.ask_question("Please describe the change you want to make (one at the time please)")
|
||||
change_description = user_description.text
|
||||
|
||||
elif user_response.button == "bug":
|
||||
user_description = await self.ask_question("Please describe the issue you found (one at the time please)")
|
||||
bug_report = user_description.text
|
||||
|
||||
return should_iterate, is_loop, bug_report, change_description
|
||||
|
||||
def try_next_alternative_solution(self, user_feedback: str, user_feedback_qa: list[str]) -> AgentResponse:
|
||||
"""
|
||||
Call the ProblemSolver to try an alternative solution.
|
||||
|
||||
Stores the user feedback and sets iteration state (not completed, no description)
|
||||
so that ProblemSolver will be triggered.
|
||||
Stores the user feedback and sets iteration state so that ProblemSolver will be triggered.
|
||||
|
||||
:param user_feedback: User feedback to store in the iteration state.
|
||||
:param user_feedback_qa: Additional questions/answers about the problem.
|
||||
@@ -238,7 +283,7 @@ class Troubleshooter(IterationPromptMixin, BaseAgent):
|
||||
next_state_iteration["user_feedback"] = user_feedback
|
||||
next_state_iteration["user_feedback_qa"] = user_feedback_qa
|
||||
next_state_iteration["attempts"] += 1
|
||||
next_state_iteration["completed"] = False
|
||||
next_state_iteration["status"] = IterationStatus.PROBLEM_SOLVER
|
||||
self.next_state.flag_iterations_as_modified()
|
||||
self.next_state.action = f"Alternative solution (attempt #{next_state_iteration['attempts']})"
|
||||
return AgentResponse.done(self)
|
||||
|
||||
2
core/config/magic_words.py
Normal file
2
core/config/magic_words.py
Normal file
@@ -0,0 +1,2 @@
|
||||
PROBLEM_IDENTIFIED = "PROBLEM_IDENTIFIED"
|
||||
ADD_LOGS = "ADD_LOGS"
|
||||
@@ -30,6 +30,19 @@ class TaskStatus:
|
||||
SKIPPED = "skipped"
|
||||
|
||||
|
||||
class IterationStatus:
|
||||
"""Status of an iteration."""
|
||||
|
||||
HUNTING_FOR_BUG = "check_logs"
|
||||
AWAITING_LOGGING = "awaiting_logging"
|
||||
AWAITING_USER_TEST = "awaiting_user_test"
|
||||
AWAITING_BUG_FIX = "awaiting_bug_fix"
|
||||
AWAITING_BUG_REPRODUCTION = "awaiting_bug_reproduction"
|
||||
FIND_SOLUTION = "find_solution"
|
||||
PROBLEM_SOLVER = "problem_solver"
|
||||
DONE = "done"
|
||||
|
||||
|
||||
class ProjectState(Base):
|
||||
__tablename__ = "project_states"
|
||||
__table_args__ = (
|
||||
@@ -105,7 +118,7 @@ class ProjectState(Base):
|
||||
|
||||
:return: List of unfinished iterations.
|
||||
"""
|
||||
return [iteration for iteration in self.iterations if not iteration.get("completed")]
|
||||
return [iteration for iteration in self.iterations if iteration.get("status") != IterationStatus.DONE]
|
||||
|
||||
@property
|
||||
def current_iteration(self) -> Optional[dict]:
|
||||
@@ -285,7 +298,7 @@ class ProjectState(Base):
|
||||
raise ValueError("Current state is read-only (already has a next state).")
|
||||
|
||||
log.debug(f"Completing iteration {self.unfinished_iterations[0]}")
|
||||
self.unfinished_iterations[0]["completed"] = True
|
||||
self.unfinished_iterations[0]["status"] = IterationStatus.DONE
|
||||
self.flag_iterations_as_modified()
|
||||
|
||||
def flag_iterations_as_modified(self):
|
||||
|
||||
6
core/prompts/bug-hunter/bug_found_or_add_logs.prompt
Normal file
6
core/prompts/bug-hunter/bug_found_or_add_logs.prompt
Normal file
@@ -0,0 +1,6 @@
|
||||
We are working on a solving a technical problem in a codebase and here is a conclusion from a team member:
|
||||
--- TEAM_MEMBER_CONCLUSION ---
|
||||
{{ hunt_conclusion }}
|
||||
--- END_OF_TEAM_MEMBER_CONCLUSION ---
|
||||
|
||||
Please tell me if the conclusion from the team member is to add more logs around the code or if the conclusion is that there are all information needed to fix the issue.
|
||||
@@ -0,0 +1,31 @@
|
||||
You are working on an app called "{{ state.branch.project.name }}" and you need to write code for the entire application.
|
||||
|
||||
{% include "partials/project_details.prompt" %}
|
||||
|
||||
{% if state.tasks and state.current_task %}
|
||||
Development process of this app was split into smaller tasks. Here is the list of all tasks:
|
||||
```{% for task in state.tasks %}
|
||||
{{ loop.index }}. {{ task.description }}
|
||||
{% endfor %}
|
||||
```
|
||||
|
||||
You are currently working on, and have to focus only on, this task:
|
||||
```
|
||||
{{ current_task.description }}
|
||||
```
|
||||
|
||||
{% endif %}
|
||||
A part of the app is already finished.
|
||||
{% include "partials/files_list.prompt" %}
|
||||
|
||||
{% include "partials/user_feedback.prompt" %}
|
||||
|
||||
{% if next_solution_to_try is not none %}
|
||||
Focus on solving this issue in the following way:
|
||||
```
|
||||
{{ next_solution_to_try }}
|
||||
```
|
||||
{% endif %}
|
||||
{#{% include "partials/doc_snippets.prompt" %}#}
|
||||
|
||||
Based on this information, you need to tell me in 2-3 sentences how can I reproduce the issue that the user experienced.
|
||||
43
core/prompts/bug-hunter/iteration.prompt
Normal file
43
core/prompts/bug-hunter/iteration.prompt
Normal file
@@ -0,0 +1,43 @@
|
||||
You are working on an app called "{{ state.branch.project.name }}" and you need to write code for the entire application.
|
||||
|
||||
{% include "partials/project_details.prompt" %}
|
||||
|
||||
{% if state.tasks and state.current_task %}
|
||||
Development process of this app was split into smaller tasks. Here is the list of all tasks:
|
||||
```{% for task in state.tasks %}
|
||||
{{ loop.index }}. {{ task.description }}
|
||||
{% endfor %}
|
||||
```
|
||||
|
||||
You are currently working on, and have to focus only on, this task:
|
||||
```
|
||||
{{ current_task.description }}
|
||||
```
|
||||
|
||||
{% endif %}
|
||||
A part of the app is already finished.
|
||||
{% include "partials/files_list.prompt" %}
|
||||
|
||||
{% include "partials/user_feedback.prompt" %}
|
||||
|
||||
{% if next_solution_to_try is not none %}
|
||||
Focus on solving this issue in the following way:
|
||||
```
|
||||
{{ next_solution_to_try }}
|
||||
```
|
||||
{% endif %}
|
||||
{#{% include "partials/doc_snippets.prompt" %}#}
|
||||
|
||||
Based on this information, you need to figure out where is the problem that the user described. You have 2 options - to tell me exactly where is the problem happening or to add more logs to better determine where is the problem. If you think we should add more logs around the code to better understand the problem, tell me code snippets in which we should add the logs. If you think you know where the issue is, don't add any new logs but explain what log print tell point you to the problem, what the problem is, what is the solution to this problem and how the solution will fix the problem. What is your answer?
|
||||
|
||||
{#**IMPORTANT**
|
||||
I you decide to add logs, start the message with "{{ magic_words.ADD_LOGS }}". Make sure that the log starts with "PYTHAGORA_DEBUGGING_LOG". For example, if you want to add a log in a Python code `print("Hello, World!")`, you should put `print("PYTHAGORA_DEBUGGING_LOG: Hello, World!")` instead. This refers to **ONLY** the logs you want to add that are not in the codebase at the moment. All logs that are in the codebase already, you must not change. When you write code, you **MUST NOT** add any new lines of code except the logs.
|
||||
|
||||
Your message will be read by a developer so you don't have to write the entire files in which you want to add logs to. Just write the lines of code where you want to add logs so that the developer reading your message understands where should they put the logs to.
|
||||
|
||||
**IMPORTANT**
|
||||
If you have all the information you need to solve the problem, do not add any logs but explain what is the problem, where in the code is it and how should it be fixed and end the response with with "{{ magic_words.PROBLEM_IDENTIFIED }}". Remember, if you mentioned to add any logs, you **MUST NOT** say "{{ magic_words.PROBLEM_IDENTIFIED }}".
|
||||
|
||||
{% include "partials/file_naming.prompt" %}
|
||||
{% include "partials/relative_paths.prompt" %}
|
||||
#}
|
||||
12
core/prompts/bug-hunter/log_data.prompt
Normal file
12
core/prompts/bug-hunter/log_data.prompt
Normal file
@@ -0,0 +1,12 @@
|
||||
{% if backend_logs is not none %}Here are the logs we added to the backend:
|
||||
```
|
||||
{{ backend_logs }}
|
||||
```
|
||||
{% endif %}{% if frontend_logs is not none %}
|
||||
Here are the logs we added to the frontend:
|
||||
```
|
||||
{{ frontend_logs }}
|
||||
```
|
||||
{% endif %}{% if fix_attempted %}
|
||||
The problem wasn't solved with the last changes. You have 2 options - to tell me exactly where is the problem happening or to add more logs to better determine where is the problem. If you think we should add more logs around the code to better understand the problem, tell me code snippets in which we should add the logs. If you think you know where the issue is, don't add any new logs but explain what log print tell point you to the problem, what the problem is, what is the solution to this problem and how the solution will fix the problem. What is your answer?
|
||||
{% endif %}
|
||||
43
core/prompts/bug-hunter/parse_task.prompt
Normal file
43
core/prompts/bug-hunter/parse_task.prompt
Normal file
@@ -0,0 +1,43 @@
|
||||
Ok, now, take your response and convert it to a list of actionable steps that will be executed by a machine.
|
||||
Analyze the entire message, think step by step and make sure that you don't omit any information
|
||||
when converting this message to steps.
|
||||
|
||||
Each step can be either:
|
||||
|
||||
* `command` - command to run (must be able to run on a {{ os }} machine, assume current working directory is project root folder)
|
||||
* `save_file` - create or update ONE file (only provide file path, not contents)
|
||||
* `human_intervention` - if you need the human to do something, use this type of step and explain in details what you want the human to do. NEVER use `human_intervention` for testing, as testing will be done separately by a dedicated QA after all the steps are done. Also you MUST NOT use `human_intervention` to ask the human to write or review code.
|
||||
|
||||
**IMPORTANT**: If multiple changes are required for same file, you must provide single `save_file` step for each file.
|
||||
|
||||
{% include "partials/file_naming.prompt" %}
|
||||
{% include "partials/relative_paths.prompt" %}
|
||||
{% include "partials/execution_order.prompt" %}
|
||||
{% include "partials/human_intervention_explanation.prompt" %}
|
||||
|
||||
**IMPORTANT**: Remember, NEVER output human intervention steps to do manual tests or coding tasks, even if the previous message asks for it! The testing will be done *after* these steps and you MUST NOT include testing in these steps.
|
||||
|
||||
Examples:
|
||||
------------------------example_1---------------------------
|
||||
```
|
||||
{
|
||||
"tasks": [
|
||||
{
|
||||
"type": "save_file",
|
||||
"save_file": {
|
||||
"path": "server.js"
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "command",
|
||||
"command": {
|
||||
"command": "mv index.js public/index.js"",
|
||||
"timeout": 5,
|
||||
"success_message": "",
|
||||
"command_id": "move_index_file"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
------------------------end_of_example_1---------------------------
|
||||
0
core/prompts/bug-hunter/system.prompt
Normal file
0
core/prompts/bug-hunter/system.prompt
Normal file
Reference in New Issue
Block a user