mirror of
https://github.com/Pythagora-io/gpt-pilot.git
synced 2026-01-09 13:17:55 -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.
127 lines
5.0 KiB
Python
127 lines
5.0 KiB
Python
from typing import Optional
|
|
|
|
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.agents.troubleshooter import IterationPromptMixin
|
|
from core.llm.parser import JSONParser
|
|
from core.log import get_logger
|
|
|
|
log = get_logger(__name__)
|
|
|
|
|
|
class AlternativeSolutions(BaseModel):
|
|
# FIXME: This is probably extra leftover from some dead code in the old implementation
|
|
description_of_tried_solutions: str = Field(
|
|
description="A description of the solutions that were tried to solve the recurring issue that was labeled as loop by the user.",
|
|
)
|
|
alternative_solutions: list[str] = Field(
|
|
description=("List of all alternative solutions to the recurring issue that was labeled as loop by the user.")
|
|
)
|
|
|
|
|
|
class ProblemSolver(IterationPromptMixin, BaseAgent):
|
|
agent_type = "problem-solver"
|
|
display_name = "Problem Solver"
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
self.iteration = self.current_state.current_iteration
|
|
self.next_state_iteration = self.next_state.current_iteration
|
|
self.previous_solutions = [s for s in self.iteration["alternative_solutions"] if s["tried"]]
|
|
self.possible_solutions = [s for s in self.iteration["alternative_solutions"] if not s["tried"]]
|
|
|
|
async def run(self) -> AgentResponse:
|
|
if self.iteration is None:
|
|
log.warning("ProblemSolver agent started without an iteration to work on, possible bug?")
|
|
return AgentResponse.done(self)
|
|
|
|
if not self.possible_solutions:
|
|
await self.generate_alternative_solutions()
|
|
return AgentResponse.done(self)
|
|
|
|
return await self.try_alternative_solutions()
|
|
|
|
async def generate_alternative_solutions(self):
|
|
llm = self.get_llm()
|
|
convo = (
|
|
AgentConvo(self)
|
|
.template(
|
|
"get_alternative_solutions",
|
|
user_input=self.iteration["user_feedback"],
|
|
iteration=self.iteration,
|
|
previous_solutions=self.previous_solutions,
|
|
)
|
|
.require_schema(AlternativeSolutions)
|
|
)
|
|
llm_response: AlternativeSolutions = await llm(
|
|
convo,
|
|
parser=JSONParser(spec=AlternativeSolutions),
|
|
temperature=1,
|
|
)
|
|
self.next_state_iteration["alternative_solutions"] = self.iteration["alternative_solutions"] + [
|
|
{
|
|
"user_feedback": None,
|
|
"description": solution,
|
|
"tried": False,
|
|
}
|
|
for solution in llm_response.alternative_solutions
|
|
]
|
|
self.next_state.flag_iterations_as_modified()
|
|
|
|
async def try_alternative_solutions(self) -> AgentResponse:
|
|
preferred_solution = await self.ask_for_preferred_solution()
|
|
if preferred_solution is None:
|
|
# TODO: We have several alternative solutions but the user didn't choose any.
|
|
# This means the user either needs expert help, or that they need to go back and
|
|
# maybe rephrase the tasks or even the project specs.
|
|
# For now, we'll just mark these as not working and try to regenerate.
|
|
self.next_state_iteration["alternative_solutions"] = [
|
|
{
|
|
**s,
|
|
"tried": True,
|
|
"user_feedback": s["user_feedback"] or "That doesn't sound like a good idea, try something else.",
|
|
}
|
|
for s in self.possible_solutions
|
|
]
|
|
self.next_state.flag_iterations_as_modified()
|
|
return AgentResponse.done(self)
|
|
|
|
index, next_solution_to_try = preferred_solution
|
|
llm_solution = await self.find_solution(
|
|
self.iteration["user_feedback"],
|
|
next_solution_to_try=next_solution_to_try,
|
|
)
|
|
|
|
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.flag_iterations_as_modified()
|
|
return AgentResponse.done(self)
|
|
|
|
async def ask_for_preferred_solution(self) -> Optional[tuple[int, str]]:
|
|
solutions = self.possible_solutions
|
|
buttons = {}
|
|
|
|
for i in range(len(solutions)):
|
|
buttons[str(i)] = str(i + 1)
|
|
buttons["none"] = "None of these"
|
|
|
|
solutions_txt = "\n\n".join([f"{i+1}: {s['description']}" for i, s in enumerate(solutions)])
|
|
user_response = await self.ask_question(
|
|
"Choose which solution would you like Pythagora to try next:\n\n" + solutions_txt,
|
|
buttons=buttons,
|
|
default="0",
|
|
buttons_only=True,
|
|
)
|
|
if user_response.button == "none" or user_response.cancelled:
|
|
return None
|
|
|
|
try:
|
|
i = int(user_response.button)
|
|
return i, solutions[i]
|
|
except (ValueError, IndexError):
|
|
return None
|