From 376ed5fdafa8a2f67edf94a76b275d01ebbbc239 Mon Sep 17 00:00:00 2001 From: matija-ilijas Date: Wed, 24 Jul 2024 11:52:06 +0200 Subject: [PATCH 1/6] Added initial add feature functionality --- core/agents/orchestrator.py | 3 +- core/agents/response.py | 7 ++++ core/agents/spec_writer.py | 41 ++++++++++++++++++- .../spec-writer/add_new_feature.prompt | 21 ++++++++++ 4 files changed, 70 insertions(+), 2 deletions(-) create mode 100644 core/prompts/spec-writer/add_new_feature.prompt diff --git a/core/agents/orchestrator.py b/core/agents/orchestrator.py index 0c31132ff..3966020ad 100644 --- a/core/agents/orchestrator.py +++ b/core/agents/orchestrator.py @@ -182,7 +182,8 @@ def create_agent(self, prev_response: Optional[AgentResponse]) -> BaseAgent: return Importer(self.state_manager, self.ui, prev_response=prev_response) if prev_response.type == ResponseType.EXTERNAL_DOCS_REQUIRED: return ExternalDocumentation(self.state_manager, self.ui, prev_response=prev_response) - + if prev_response.type == ResponseType.NEW_FEATURE_REQUESTED: + return SpecWriter(self.state_manager, self.ui, prev_response=prev_response) if not state.specification.description: if state.files: # The project has been imported, but not analyzed yet diff --git a/core/agents/response.py b/core/agents/response.py index d41843367..7b777eb6d 100644 --- a/core/agents/response.py +++ b/core/agents/response.py @@ -45,6 +45,9 @@ class ResponseType(str, Enum): EXTERNAL_DOCS_REQUIRED = "external-docs-required" """We need to fetch external docs for a task.""" + NEW_FEATURE_REQUESTED = "new-feature-requested" + """User wants to add a new feature to project specification.""" + class AgentResponse: type: ResponseType = ResponseType.DONE @@ -144,3 +147,7 @@ def import_project(agent: "BaseAgent") -> "AgentResponse": @staticmethod def external_docs_required(agent: "BaseAgent") -> "AgentResponse": return AgentResponse(type=ResponseType.EXTERNAL_DOCS_REQUIRED, agent=agent) + + @staticmethod + def new_feature_requested(agent: "BaseAgent") -> "AgentResponse": + return AgentResponse(type=ResponseType.NEW_FEATURE_REQUESTED, agent=agent) diff --git a/core/agents/spec_writer.py b/core/agents/spec_writer.py index 5002f734c..d586e28ff 100644 --- a/core/agents/spec_writer.py +++ b/core/agents/spec_writer.py @@ -1,6 +1,6 @@ from core.agents.base import BaseAgent from core.agents.convo import AgentConvo -from core.agents.response import AgentResponse +from core.agents.response import AgentResponse, ResponseType from core.db.models import Complexity from core.llm.parser import StringParser from core.log import get_logger @@ -26,6 +26,12 @@ class SpecWriter(BaseAgent): display_name = "Spec Writer" async def run(self) -> AgentResponse: + if self.prev_response and self.prev_response.type == ResponseType.NEW_FEATURE_REQUESTED: + return await self.update_spec() + else: + return await self.initialize_spec() + + async def initialize_spec(self) -> AgentResponse: response = await self.ask_question( "Describe your app in as much detail as possible", allow_empty=False, @@ -72,6 +78,39 @@ async def run(self) -> AgentResponse: telemetry.set("initial_prompt", spec) telemetry.set("is_complex_app", complexity != Complexity.SIMPLE) + self.next_state.action = SPEC_STEP_NAME + return AgentResponse.new_feature_requested(self) + + async def update_spec(self) -> AgentResponse: + response = await self.ask_question( + "Describe the new feature you want to add", + allow_empty=False, + buttons={ + # FIXME: must be lowercase becase VSCode doesn't recognize it otherwise. Needs a fix in the extension + "cancel": "cancel", + }, + ) + if response.cancelled: + return AgentResponse.error(self, "No feature description") + + if response.button == "cancel": + return AgentResponse.error(self, "No feature description") + + feature_description = response.text.strip() + + await self.send_message("Adding new feature to project specification ...") + llm = self.get_llm() + convo = AgentConvo(self).template("add_new_feature", feature_description=feature_description) + llm_response: str = await llm(convo, temperature=0, parser=StringParser()) + updated_spec = llm_response.strip() + + self.next_state.specification = self.current_state.specification.clone() + self.next_state.specification.description = updated_spec + + telemetry.set( + "initial_prompt", updated_spec + ) # TODO Check if a separate telemetry variable needed for updated spec + self.next_state.action = SPEC_STEP_NAME return AgentResponse.done(self) diff --git a/core/prompts/spec-writer/add_new_feature.prompt b/core/prompts/spec-writer/add_new_feature.prompt new file mode 100644 index 000000000..395efe3b9 --- /dev/null +++ b/core/prompts/spec-writer/add_new_feature.prompt @@ -0,0 +1,21 @@ +Your team has taken the client brief and turned it into a project specification. +Afterwards the client added a description for a new feature to be added to the project specification. +Your job is to update the project specification so that it contains the new feature information but does not lack any of the information from the original project specification. + +This might include: +* details on how the app should work +* information which 3rd party packages or APIs to use or avoid +* concrete examples of API requests/responses, library usage, or other external documentation + +Here is the client brief: +---PROJECT-SPECIFICATION-START--- +{{ state.specification.description }} +---PROJECT-SPECIFICATION-END--- + +Here is the specification your team came up with: +---FEATURE-DESCRIPTION-START--- +{{ feature_description }} +---FEATURE-DESCRIPTION-END--- + +In your response, output the new updated project specification. +If there is no feature description, just output the original project specification. From 17bdaf2fb33cdbbc6495dda3f8f258b9c4b7ac14 Mon Sep 17 00:00:00 2001 From: matija-ilijas Date: Thu, 25 Jul 2024 14:43:38 +0200 Subject: [PATCH 2/6] Added new feature request in deep focus pipeline --- core/agents/orchestrator.py | 5 +++-- core/agents/response.py | 3 --- core/agents/spec_writer.py | 28 ++++++++-------------------- core/agents/troubleshooter.py | 4 ++-- core/db/models/project_state.py | 1 + 5 files changed, 14 insertions(+), 27 deletions(-) diff --git a/core/agents/orchestrator.py b/core/agents/orchestrator.py index 3966020ad..475f37ac0 100644 --- a/core/agents/orchestrator.py +++ b/core/agents/orchestrator.py @@ -182,8 +182,6 @@ def create_agent(self, prev_response: Optional[AgentResponse]) -> BaseAgent: return Importer(self.state_manager, self.ui, prev_response=prev_response) if prev_response.type == ResponseType.EXTERNAL_DOCS_REQUIRED: return ExternalDocumentation(self.state_manager, self.ui, prev_response=prev_response) - if prev_response.type == ResponseType.NEW_FEATURE_REQUESTED: - return SpecWriter(self.state_manager, self.ui, prev_response=prev_response) if not state.specification.description: if state.files: # The project has been imported, but not analyzed yet @@ -253,6 +251,9 @@ def create_agent(self, prev_response: Optional[AgentResponse]) -> BaseAgent: elif 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) + elif current_iteration_status == IterationStatus.NEW_FEATURE_REQUESTED: + # Call Spec Writer to add the "change" requested by the user to project specification + return SpecWriter(self.state_manager, self.ui) # We have just finished the task, call Troubleshooter to ask the user to review return Troubleshooter(self.state_manager, self.ui) diff --git a/core/agents/response.py b/core/agents/response.py index 7b777eb6d..4a4855f41 100644 --- a/core/agents/response.py +++ b/core/agents/response.py @@ -45,9 +45,6 @@ class ResponseType(str, Enum): EXTERNAL_DOCS_REQUIRED = "external-docs-required" """We need to fetch external docs for a task.""" - NEW_FEATURE_REQUESTED = "new-feature-requested" - """User wants to add a new feature to project specification.""" - class AgentResponse: type: ResponseType = ResponseType.DONE diff --git a/core/agents/spec_writer.py b/core/agents/spec_writer.py index d586e28ff..3265ba046 100644 --- a/core/agents/spec_writer.py +++ b/core/agents/spec_writer.py @@ -1,6 +1,7 @@ from core.agents.base import BaseAgent from core.agents.convo import AgentConvo -from core.agents.response import AgentResponse, ResponseType +from core.agents.response import AgentResponse +from core.db.models.project_state import IterationStatus from core.db.models import Complexity from core.llm.parser import StringParser from core.log import get_logger @@ -26,7 +27,8 @@ class SpecWriter(BaseAgent): display_name = "Spec Writer" async def run(self) -> AgentResponse: - if self.prev_response and self.prev_response.type == ResponseType.NEW_FEATURE_REQUESTED: + current_iteration = self.current_state.current_iteration + if current_iteration is not None and current_iteration["status"] == IterationStatus.NEW_FEATURE_REQUESTED: return await self.update_spec() else: return await self.initialize_spec() @@ -79,25 +81,10 @@ async def initialize_spec(self) -> AgentResponse: telemetry.set("is_complex_app", complexity != Complexity.SIMPLE) self.next_state.action = SPEC_STEP_NAME - return AgentResponse.new_feature_requested(self) + return AgentResponse.done(self) async def update_spec(self) -> AgentResponse: - response = await self.ask_question( - "Describe the new feature you want to add", - allow_empty=False, - buttons={ - # FIXME: must be lowercase becase VSCode doesn't recognize it otherwise. Needs a fix in the extension - "cancel": "cancel", - }, - ) - if response.cancelled: - return AgentResponse.error(self, "No feature description") - - if response.button == "cancel": - return AgentResponse.error(self, "No feature description") - - feature_description = response.text.strip() - + feature_description = self.current_state.current_iteration["description"] await self.send_message("Adding new feature to project specification ...") llm = self.get_llm() convo = AgentConvo(self).template("add_new_feature", feature_description=feature_description) @@ -111,7 +98,8 @@ async def update_spec(self) -> AgentResponse: "initial_prompt", updated_spec ) # TODO Check if a separate telemetry variable needed for updated spec - self.next_state.action = SPEC_STEP_NAME + self.next_state.current_iteration["status"] = IterationStatus.FIND_SOLUTION + self.next_state.flag_iterations_as_modified() return AgentResponse.done(self) async def check_prompt_complexity(self, prompt: str) -> str: diff --git a/core/agents/troubleshooter.py b/core/agents/troubleshooter.py index 6a3ebcccf..d6b615963 100644 --- a/core/agents/troubleshooter.py +++ b/core/agents/troubleshooter.py @@ -87,7 +87,7 @@ async def create_iteration(self) -> AgentResponse: 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) + user_feedback_qa = None # await self.generate_bug_report(run_command, user_instructions, user_feedback) if is_loop: if last_iteration["alternative_solutions"]: @@ -102,7 +102,7 @@ async def create_iteration(self) -> AgentResponse: else: # should be - elif change_description is not None: - but to prevent bugs with the extension # this might be caused if we show the input field instead of buttons - iteration_status = IterationStatus.FIND_SOLUTION + iteration_status = IterationStatus.NEW_FEATURE_REQUESTED self.next_state.iterations = self.current_state.iterations + [ { diff --git a/core/db/models/project_state.py b/core/db/models/project_state.py index 5d4db95f6..67ad685ad 100644 --- a/core/db/models/project_state.py +++ b/core/db/models/project_state.py @@ -41,6 +41,7 @@ class IterationStatus: IMPLEMENT_SOLUTION = "implement_solution" FIND_SOLUTION = "find_solution" PROBLEM_SOLVER = "problem_solver" + NEW_FEATURE_REQUESTED = "new_feature_requested" DONE = "done" From 3043b9301ce851bfea2b43561da705decea31aab Mon Sep 17 00:00:00 2001 From: matija-ilijas Date: Thu, 25 Jul 2024 16:20:11 +0200 Subject: [PATCH 3/6] Minor formatting --- core/agents/spec_writer.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/core/agents/spec_writer.py b/core/agents/spec_writer.py index 3265ba046..f1222fc84 100644 --- a/core/agents/spec_writer.py +++ b/core/agents/spec_writer.py @@ -1,8 +1,8 @@ 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.db.models import Complexity +from core.db.models.project_state import IterationStatus from core.llm.parser import StringParser from core.log import get_logger from core.telemetry import telemetry @@ -85,7 +85,9 @@ async def initialize_spec(self) -> AgentResponse: async def update_spec(self) -> AgentResponse: feature_description = self.current_state.current_iteration["description"] - await self.send_message("Adding new feature to project specification ...") + await self.send_message( + f"Adding feature with the following description to project specification:\n\n{feature_description}" + ) llm = self.get_llm() convo = AgentConvo(self).template("add_new_feature", feature_description=feature_description) llm_response: str = await llm(convo, temperature=0, parser=StringParser()) From 5e2cf85addaec5496de6bd94be29f8614d573d8a Mon Sep 17 00:00:00 2001 From: matija-ilijas Date: Thu, 25 Jul 2024 20:51:42 +0200 Subject: [PATCH 4/6] Minor fixes and database update --- .pre-commit-config.yaml | 4 +-- core/agents/response.py | 4 --- core/agents/spec_writer.py | 9 +++-- core/agents/tech_lead.py | 3 +- core/agents/troubleshooter.py | 2 +- ..._add_original_description_and_template_.py | 36 +++++++++++++++++++ core/db/models/specification.py | 4 +++ core/telemetry/__init__.py | 2 ++ 8 files changed, 51 insertions(+), 13 deletions(-) create mode 100644 core/db/migrations/versions/c8905d4ce784_add_original_description_and_template_.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 975ececd9..5695bb827 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,7 +16,7 @@ repos: name: alembic stages: [commit] types: [python] - entry: alembic -c core/db/alembic.ini check + entry: /Users/milijas/Code/PythagoraDev/gpt-pilot/venv/bin/alembic -c core/db/alembic.ini check language: system pass_filenames: false - repo: local @@ -26,6 +26,6 @@ repos: name: pytest stages: [commit] types: [python] - entry: pytest + entry: /Users/milijas/Code/PythagoraDev/gpt-pilot/venv/bin/pytest language: system pass_filenames: false diff --git a/core/agents/response.py b/core/agents/response.py index 4a4855f41..d41843367 100644 --- a/core/agents/response.py +++ b/core/agents/response.py @@ -144,7 +144,3 @@ def import_project(agent: "BaseAgent") -> "AgentResponse": @staticmethod def external_docs_required(agent: "BaseAgent") -> "AgentResponse": return AgentResponse(type=ResponseType.EXTERNAL_DOCS_REQUIRED, agent=agent) - - @staticmethod - def new_feature_requested(agent: "BaseAgent") -> "AgentResponse": - return AgentResponse(type=ResponseType.NEW_FEATURE_REQUESTED, agent=agent) diff --git a/core/agents/spec_writer.py b/core/agents/spec_writer.py index f1222fc84..a53529dcd 100644 --- a/core/agents/spec_writer.py +++ b/core/agents/spec_writer.py @@ -28,7 +28,7 @@ class SpecWriter(BaseAgent): async def run(self) -> AgentResponse: current_iteration = self.current_state.current_iteration - if current_iteration is not None and current_iteration["status"] == IterationStatus.NEW_FEATURE_REQUESTED: + if current_iteration is not None and current_iteration.get("status") == IterationStatus.NEW_FEATURE_REQUESTED: return await self.update_spec() else: return await self.initialize_spec() @@ -75,6 +75,7 @@ async def initialize_spec(self) -> AgentResponse: spec = await self.review_spec(spec) self.next_state.specification = self.current_state.specification.clone() + self.next_state.specification.original_description = spec self.next_state.specification.description = spec self.next_state.specification.complexity = complexity telemetry.set("initial_prompt", spec) @@ -84,7 +85,7 @@ async def initialize_spec(self) -> AgentResponse: return AgentResponse.done(self) async def update_spec(self) -> AgentResponse: - feature_description = self.current_state.current_iteration["description"] + feature_description = self.current_state.current_iteration["user_feedback"] await self.send_message( f"Adding feature with the following description to project specification:\n\n{feature_description}" ) @@ -96,9 +97,7 @@ async def update_spec(self) -> AgentResponse: self.next_state.specification = self.current_state.specification.clone() self.next_state.specification.description = updated_spec - telemetry.set( - "initial_prompt", updated_spec - ) # TODO Check if a separate telemetry variable needed for updated spec + telemetry.set("updated_prompt", updated_spec) self.next_state.current_iteration["status"] = IterationStatus.FIND_SOLUTION self.next_state.flag_iterations_as_modified() diff --git a/core/agents/tech_lead.py b/core/agents/tech_lead.py index 69464a52e..5e572b17d 100644 --- a/core/agents/tech_lead.py +++ b/core/agents/tech_lead.py @@ -107,7 +107,8 @@ async def apply_project_templates(self): if summaries: spec = self.current_state.specification.clone() - spec.description += "\n\n" + "\n\n".join(summaries) + spec.template_summary = "\n\n".join(summaries) + self.next_state.specification = spec async def ask_for_new_feature(self) -> AgentResponse: diff --git a/core/agents/troubleshooter.py b/core/agents/troubleshooter.py index d6b615963..dc753b53c 100644 --- a/core/agents/troubleshooter.py +++ b/core/agents/troubleshooter.py @@ -109,7 +109,7 @@ async def create_iteration(self) -> AgentResponse: "id": uuid4().hex, "user_feedback": user_feedback, "user_feedback_qa": user_feedback_qa, - "description": change_description, + "description": None, "alternative_solutions": [], # FIXME - this is incorrect if this is a new problem; otherwise we could # just count the iterations diff --git a/core/db/migrations/versions/c8905d4ce784_add_original_description_and_template_.py b/core/db/migrations/versions/c8905d4ce784_add_original_description_and_template_.py new file mode 100644 index 000000000..5d76dba8c --- /dev/null +++ b/core/db/migrations/versions/c8905d4ce784_add_original_description_and_template_.py @@ -0,0 +1,36 @@ +"""Add original description and template summary fields to specifications + +Revision ID: c8905d4ce784 +Revises: 08d71952ec2f +Create Date: 2024-07-25 19:24:23.808237 + +""" + +from typing import Sequence, Union + +import sqlalchemy as sa +from alembic import op + +# revision identifiers, used by Alembic. +revision: str = "c8905d4ce784" +down_revision: Union[str, None] = "08d71952ec2f" +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table("specifications", schema=None) as batch_op: + batch_op.add_column(sa.Column("original_description", sa.String(), nullable=True)) + batch_op.add_column(sa.Column("template_summary", sa.String(), nullable=True)) + + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table("specifications", schema=None) as batch_op: + batch_op.drop_column("template_summary") + batch_op.drop_column("original_description") + + # ### end Alembic commands ### diff --git a/core/db/models/specification.py b/core/db/models/specification.py index 99eb54037..7631fdeba 100644 --- a/core/db/models/specification.py +++ b/core/db/models/specification.py @@ -26,7 +26,9 @@ class Specification(Base): id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True) # Attributes + original_description: Mapped[Optional[str]] = mapped_column() description: Mapped[str] = mapped_column(default="") + template_summary: Mapped[Optional[str]] = mapped_column() architecture: Mapped[str] = mapped_column(default="") system_dependencies: Mapped[list[dict]] = mapped_column(default=list) package_dependencies: Mapped[list[dict]] = mapped_column(default=list) @@ -43,7 +45,9 @@ def clone(self) -> "Specification": Clone the specification. """ clone = Specification( + original_description=self.original_description, description=self.description, + template_summary=self.template_summary, architecture=self.architecture, system_dependencies=self.system_dependencies, package_dependencies=self.package_dependencies, diff --git a/core/telemetry/__init__.py b/core/telemetry/__init__.py index 89bd68a81..16e62f608 100644 --- a/core/telemetry/__init__.py +++ b/core/telemetry/__init__.py @@ -82,6 +82,8 @@ def clear_data(self): "model": config.agent["default"].model, # Initial prompt "initial_prompt": None, + # Updated prompt + "updated_prompt": None, # App complexity "is_complex_app": None, # Optional template used for the project From c0e27081b1b4fbb03c079310cff1a92376c7ecbc Mon Sep 17 00:00:00 2001 From: matija-ilijas Date: Fri, 26 Jul 2024 11:52:46 +0200 Subject: [PATCH 5/6] Changed back pre-commit-config-yaml --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5695bb827..975ececd9 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,7 +16,7 @@ repos: name: alembic stages: [commit] types: [python] - entry: /Users/milijas/Code/PythagoraDev/gpt-pilot/venv/bin/alembic -c core/db/alembic.ini check + entry: alembic -c core/db/alembic.ini check language: system pass_filenames: false - repo: local @@ -26,6 +26,6 @@ repos: name: pytest stages: [commit] types: [python] - entry: /Users/milijas/Code/PythagoraDev/gpt-pilot/venv/bin/pytest + entry: pytest language: system pass_filenames: false From a7b0ad12cb4a67996f98e4b4e87d0b193eeb10f1 Mon Sep 17 00:00:00 2001 From: matija-ilijas Date: Fri, 26 Jul 2024 19:45:56 +0200 Subject: [PATCH 6/6] Added specification update to new feature add on app load and diff display on ui --- core/agents/orchestrator.py | 3 ++ core/agents/response.py | 13 ++++++ core/agents/spec_writer.py | 41 ++++++++++++++----- core/agents/tech_lead.py | 5 +-- core/agents/troubleshooter.py | 8 ++-- .../spec-writer/add_new_feature.prompt | 10 ++--- core/ui/ipc_client.py | 15 +++++++ tests/agents/test_tech_lead.py | 2 +- 8 files changed, 71 insertions(+), 26 deletions(-) diff --git a/core/agents/orchestrator.py b/core/agents/orchestrator.py index 475f37ac0..2b6bdc439 100644 --- a/core/agents/orchestrator.py +++ b/core/agents/orchestrator.py @@ -182,6 +182,9 @@ def create_agent(self, prev_response: Optional[AgentResponse]) -> BaseAgent: return Importer(self.state_manager, self.ui, prev_response=prev_response) if prev_response.type == ResponseType.EXTERNAL_DOCS_REQUIRED: return ExternalDocumentation(self.state_manager, self.ui, prev_response=prev_response) + if prev_response.type == ResponseType.UPDATE_SPECIFICATION: + return SpecWriter(self.state_manager, self.ui, prev_response=prev_response) + if not state.specification.description: if state.files: # The project has been imported, but not analyzed yet diff --git a/core/agents/response.py b/core/agents/response.py index d41843367..3fa0d61cd 100644 --- a/core/agents/response.py +++ b/core/agents/response.py @@ -45,6 +45,9 @@ class ResponseType(str, Enum): EXTERNAL_DOCS_REQUIRED = "external-docs-required" """We need to fetch external docs for a task.""" + UPDATE_SPECIFICATION = "update-specification" + """We need to update the project specification.""" + class AgentResponse: type: ResponseType = ResponseType.DONE @@ -144,3 +147,13 @@ def import_project(agent: "BaseAgent") -> "AgentResponse": @staticmethod def external_docs_required(agent: "BaseAgent") -> "AgentResponse": return AgentResponse(type=ResponseType.EXTERNAL_DOCS_REQUIRED, agent=agent) + + @staticmethod + def update_specification(agent: "BaseAgent", description: str) -> "AgentResponse": + return AgentResponse( + type=ResponseType.UPDATE_SPECIFICATION, + agent=agent, + data={ + "description": description, + }, + ) diff --git a/core/agents/spec_writer.py b/core/agents/spec_writer.py index a53529dcd..9e5494f57 100644 --- a/core/agents/spec_writer.py +++ b/core/agents/spec_writer.py @@ -1,6 +1,6 @@ from core.agents.base import BaseAgent from core.agents.convo import AgentConvo -from core.agents.response import AgentResponse +from core.agents.response import AgentResponse, ResponseType from core.db.models import Complexity from core.db.models.project_state import IterationStatus from core.llm.parser import StringParser @@ -29,7 +29,9 @@ class SpecWriter(BaseAgent): async def run(self) -> AgentResponse: current_iteration = self.current_state.current_iteration if current_iteration is not None and current_iteration.get("status") == IterationStatus.NEW_FEATURE_REQUESTED: - return await self.update_spec() + return await self.update_spec(iteration_mode=True) + elif self.prev_response and self.prev_response.type == ResponseType.UPDATE_SPECIFICATION: + return await self.update_spec(iteration_mode=False) else: return await self.initialize_spec() @@ -84,23 +86,40 @@ async def initialize_spec(self) -> AgentResponse: self.next_state.action = SPEC_STEP_NAME return AgentResponse.done(self) - async def update_spec(self) -> AgentResponse: - feature_description = self.current_state.current_iteration["user_feedback"] + async def update_spec(self, iteration_mode) -> AgentResponse: + if iteration_mode: + feature_description = self.current_state.current_iteration["user_feedback"] + else: + feature_description = self.prev_response.data["description"] + await self.send_message( - f"Adding feature with the following description to project specification:\n\n{feature_description}" + f"Making the following changes to project specification:\n\n{feature_description}\n\nUpdated project specification:" ) llm = self.get_llm() convo = AgentConvo(self).template("add_new_feature", feature_description=feature_description) llm_response: str = await llm(convo, temperature=0, parser=StringParser()) updated_spec = llm_response.strip() + await self.ui.generate_diff(self.current_state.specification.description, updated_spec) + user_response = await self.ask_question( + "Do you accept these changes to the project specification?", + buttons={"yes": "Yes", "no": "No"}, + default="yes", + buttons_only=True, + ) + await self.ui.close_diff() - self.next_state.specification = self.current_state.specification.clone() - self.next_state.specification.description = updated_spec + if user_response.button == "yes": + self.next_state.specification = self.current_state.specification.clone() + self.next_state.specification.description = updated_spec + telemetry.set("updated_prompt", updated_spec) - telemetry.set("updated_prompt", updated_spec) + if iteration_mode: + self.next_state.current_iteration["status"] = IterationStatus.FIND_SOLUTION + self.next_state.flag_iterations_as_modified() + else: + complexity = await self.check_prompt_complexity(user_response.text) + self.next_state.current_epic["complexity"] = complexity - self.next_state.current_iteration["status"] = IterationStatus.FIND_SOLUTION - self.next_state.flag_iterations_as_modified() return AgentResponse.done(self) async def check_prompt_complexity(self, prompt: str) -> str: @@ -188,6 +207,6 @@ async def review_spec(self, spec: str) -> str: llm = self.get_llm() llm_response: str = await llm(convo, temperature=0) additional_info = llm_response.strip() - if additional_info: + if additional_info and len(additional_info) > 6: spec += "\nAdditional info/examples:\n" + additional_info return spec diff --git a/core/agents/tech_lead.py b/core/agents/tech_lead.py index 5e572b17d..c21732841 100644 --- a/core/agents/tech_lead.py +++ b/core/agents/tech_lead.py @@ -5,7 +5,6 @@ from core.agents.base import BaseAgent from core.agents.convo import AgentConvo from core.agents.response import AgentResponse -from core.db.models import Complexity from core.db.models.project_state import TaskStatus from core.llm.parser import JSONParser from core.log import get_logger @@ -136,12 +135,12 @@ async def ask_for_new_feature(self) -> AgentResponse: "description": response.text, "summary": None, "completed": False, - "complexity": Complexity.HARD, + "complexity": None, # Determined and defined in SpecWriter } ] # Orchestrator will rerun us to break down the new feature epic self.next_state.action = f"Start of feature #{len(self.current_state.epics)}" - return AgentResponse.done(self) + return AgentResponse.update_specification(self, response.text) async def plan_epic(self, epic) -> AgentResponse: log.debug(f"Planning tasks for the epic: {epic['name']}") diff --git a/core/agents/troubleshooter.py b/core/agents/troubleshooter.py index dc753b53c..081f7812b 100644 --- a/core/agents/troubleshooter.py +++ b/core/agents/troubleshooter.py @@ -223,7 +223,7 @@ async def get_user_feedback( is_loop = False should_iterate = True - test_message = "Can you check if the app works please?" + test_message = "Please check if the app is working" if user_instructions: hint = " Here is a description of what should be working:\n\n" + user_instructions @@ -259,13 +259,11 @@ async def get_user_feedback( is_loop = True elif user_response.button == "change": - user_description = await self.ask_question( - "Please describe the change you want to make (one at the time please)" - ) + user_description = await self.ask_question("Please describe the change you want to make (one at a time)") 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)") + user_description = await self.ask_question("Please describe the issue you found (one at a time)") bug_report = user_description.text return should_iterate, is_loop, bug_report, change_description diff --git a/core/prompts/spec-writer/add_new_feature.prompt b/core/prompts/spec-writer/add_new_feature.prompt index 395efe3b9..b9ce2599b 100644 --- a/core/prompts/spec-writer/add_new_feature.prompt +++ b/core/prompts/spec-writer/add_new_feature.prompt @@ -7,15 +7,13 @@ This might include: * information which 3rd party packages or APIs to use or avoid * concrete examples of API requests/responses, library usage, or other external documentation -Here is the client brief: ----PROJECT-SPECIFICATION-START--- +Here is the original project specification: {{ state.specification.description }} ----PROJECT-SPECIFICATION-END--- -Here is the specification your team came up with: +Here is the new feature description: ---FEATURE-DESCRIPTION-START--- {{ feature_description }} ---FEATURE-DESCRIPTION-END--- -In your response, output the new updated project specification. -If there is no feature description, just output the original project specification. +In your response, output only the new updated project specification, without any additional messages to the user. +If there is no feature description just output the original project specification. diff --git a/core/ui/ipc_client.py b/core/ui/ipc_client.py index 891574547..a3d09b9ac 100644 --- a/core/ui/ipc_client.py +++ b/core/ui/ipc_client.py @@ -42,6 +42,8 @@ class MessageType(str, Enum): IMPORT_PROJECT = "importProject" APP_FINISHED = "appFinished" FEATURE_FINISHED = "featureFinished" + GENERATE_DIFF = "generateDiff" + CLOSE_DIFF = "closeDiff" class Message(BaseModel): @@ -356,6 +358,19 @@ async def send_project_stats(self, stats: dict): content=stats, ) + async def generate_diff(self, file_old: str, file_new: str): + await self._send( + MessageType.GENERATE_DIFF, + content={ + "file_old": file_old, + "file_new": file_new, + }, + ) + + async def close_diff(self): + log.debug("Sending signal to close the generated diff file") + await self._send(MessageType.CLOSE_DIFF) + async def loading_finished(self): log.debug("Sending project loading finished signal to the extension") await self._send(MessageType.LOADING_FINISHED) diff --git a/tests/agents/test_tech_lead.py b/tests/agents/test_tech_lead.py index 892a9979a..c6a4a546f 100644 --- a/tests/agents/test_tech_lead.py +++ b/tests/agents/test_tech_lead.py @@ -56,7 +56,7 @@ async def test_ask_for_feature(agentcontext): tl = TechLead(sm, ui) response = await tl.run() - assert response.type == ResponseType.DONE + assert response.type == ResponseType.UPDATE_SPECIFICATION await sm.commit()