mirror of
https://github.com/Pythagora-io/gpt-pilot.git
synced 2026-01-08 12:53:50 -05:00
This is a complete rewrite of the GPT Pilot core, from the ground up, making the agentic architecture front and center, and also fixing some long-standing problems with the database architecture that weren't feasible to solve without breaking compatibility. As the database structure and config file syntax have changed, we have automatic imports for projects and current configs, see the README.md file for details. This also relicenses the project to FSL-1.1-MIT license.
109 lines
4.1 KiB
Python
109 lines
4.1 KiB
Python
from uuid import uuid4
|
|
|
|
from core.agents.base import BaseAgent
|
|
from core.agents.convo import AgentConvo
|
|
from core.agents.response import AgentResponse
|
|
from core.log import get_logger
|
|
|
|
log = get_logger(__name__)
|
|
|
|
|
|
class ErrorHandler(BaseAgent):
|
|
"""
|
|
Error handler agent.
|
|
|
|
Error handler is responsible for handling errors returned by other agents. If it's possible
|
|
to recover from the error, it should do it (which may include updating the "next" state) and
|
|
return DONE. Otherwise it should return EXIT to tell Orchestrator to quit the application.
|
|
"""
|
|
|
|
agent_type = "error-handler"
|
|
display_name = "Error Handler"
|
|
|
|
async def run(self) -> AgentResponse:
|
|
from core.agents.executor import Executor
|
|
from core.agents.spec_writer import SpecWriter
|
|
|
|
error = self.prev_response
|
|
if error is None:
|
|
log.warning("ErrorHandler called without a previous error", stack_info=True)
|
|
return AgentResponse.done(self)
|
|
|
|
log.error(
|
|
f"Agent {error.agent.display_name} returned error response: {error.type}",
|
|
extra={"data": error.data},
|
|
)
|
|
|
|
if isinstance(error.agent, SpecWriter):
|
|
# If SpecWriter wasn't able to get the project description, there's nothing for
|
|
# us to do.
|
|
return AgentResponse.exit(self)
|
|
|
|
if isinstance(error.agent, Executor):
|
|
return await self.handle_command_error(
|
|
error.data.get("message", "Unknown error"), error.data.get("details", {})
|
|
)
|
|
|
|
log.error(
|
|
f"Unhandled error response from agent {error.agent.display_name}",
|
|
extra={"data": error.data},
|
|
)
|
|
return AgentResponse.exit(self)
|
|
|
|
async def handle_command_error(self, message: str, details: dict) -> AgentResponse:
|
|
"""
|
|
Handle an error returned by Executor agent.
|
|
|
|
Error message must be the analyis of the command execution, and the details must contain:
|
|
* cmd - command that was executed
|
|
* timeout - timeout for the command if any (or None if no timeout was used)
|
|
* status_code - exit code for the command (or None if the command timed out)
|
|
* stdout - standard output of the command
|
|
* stderr - standard error of the command
|
|
|
|
:return: AgentResponse
|
|
"""
|
|
cmd = details.get("cmd")
|
|
timeout = details.get("timeout")
|
|
status_code = details.get("status_code")
|
|
stdout = details.get("stdout", "")
|
|
stderr = details.get("stderr", "")
|
|
|
|
if not message:
|
|
raise ValueError("No error message provided in command error response")
|
|
if not cmd:
|
|
raise ValueError("No command provided in command error response details")
|
|
|
|
llm = self.get_llm()
|
|
convo = AgentConvo(self).template(
|
|
"debug",
|
|
task_steps=self.current_state.steps,
|
|
current_task=self.current_state.current_task,
|
|
# FIXME: can this break?
|
|
step_index=self.current_state.steps.index(self.current_state.current_step),
|
|
cmd=cmd,
|
|
timeout=timeout,
|
|
stdout=stdout,
|
|
stderr=stderr,
|
|
status_code=status_code,
|
|
# fixme: everything above copypasted from Executor
|
|
analysis=message,
|
|
)
|
|
llm_response: str = await llm(convo)
|
|
|
|
# TODO: duplicate from Troubleshooter, maybe extract to a ProjectState method?
|
|
self.next_state.iterations = self.current_state.iterations + [
|
|
{
|
|
"id": uuid4().hex,
|
|
"user_feedback": f"Error running command: {cmd}",
|
|
"description": llm_response,
|
|
"alternative_solutions": [],
|
|
"attempts": 1,
|
|
"completed": False,
|
|
}
|
|
]
|
|
# TODO: maybe have ProjectState.finished_steps as well? would make the debug/ran_command prompts nicer too
|
|
self.next_state.steps = [s for s in self.current_state.steps if s.get("completed") is True]
|
|
# No need to call complete_step() here as we've just removed the steps so that Developer can break down the iteration
|
|
return AgentResponse.done(self)
|