Files
gpt-pilot/core/agents/error_handler.py
Senko Rasic 5b474ccc1f merge gpt-pilot 0.2 codebase
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.
2024-05-22 21:42:25 +02:00

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)