Repository: Pythagora-io/gpt-pilot
File: pilot/helpers/AgentConvo.py (Python)

Code before:

        Args:
            prompt_path: The path to a prompt.
            prompt_data: Data associated with the prompt.
            function_calls: Optional function calls to be included in the message.
            should_log_message: Flag if final response should be logged.
        Returns:
            The response from the agent.
        """
        # craft message
        self.construct_and_add_message_from_prompt(prompt_path, prompt_data)

        # TODO: move this if block (and the other below) to Developer agent - https://github.com/Pythagora-io/gpt-pilot/issues/91#issuecomment-1751964079
        # check if we already have the LLM response saved
        if hasattr(self.agent, 'save_dev_steps') and self.agent.save_dev_steps:
            self.agent.project.llm_req_num += 1

        self.agent.project.finish_loading()

        try:
            self.replace_files()
            response = create_gpt_chat_completion(self.messages, self.high_level_step, self.agent.project,
                                                  function_calls=function_calls, prompt_data=prompt_data,
                                                  temperature=self.temperature)
        except TokenLimitError as e:
            save_development_step(self.agent.project, prompt_path, prompt_data, self.messages, '', str(e))
            raise e

        # TODO: move this code to Developer agent - https://github.com/Pythagora-io/gpt-pilot/issues/91#issuecomment-1751964079
        if hasattr(self.agent, 'save_dev_steps') and self.agent.save_dev_steps:
            save_development_step(self.agent.project, prompt_path, prompt_data, self.messages, response)

        # TODO handle errors from OpenAI
        # It's complicated because calling functions are expecting different types of responses - string or tuple
        # https://github.com/Pythagora-io/gpt-pilot/issues/165 & #91
        if response == {} or response is None:
            # This should never happen since we're raising ApiError in create_gpt_chat_completion
            # Leaving this in place in case there's a case where this can still happen
            logger.error('Aborting with "OpenAI API error happened"')
            print(color_red_bold('There was an error talking to OpenAI API. Please try again later.'))
            payload_size_kb = len(json.dumps(self.messages)) // 1000
            raise ApiError(f"Unknown API error (prompt: {prompt_path}, request size: {payload_size_kb}KB)")

        try:
            response = parse_agent_response(response, function_calls)
        except (KeyError, json.JSONDecodeError) as err:
            logger.error("Error while parsing LLM response: {err.__class__.__name__}: {err}")
            print(color_red_bold(f'There was an error parsing LLM response: \"{err.__class__.__name__}: {err}\". Please try again later.'))
            raise ApiError(f"Error parsing LLM response: {err.__class__.__name__}: {err}: Response text: {response}") from err

        message_content = self.format_message_content(response, function_calls)

Review comment (bug):
This is the only place in the codebase where we're saving `llm_response` as a string instead of a dict.

After this crash, if the person tries to reload the project, they get:

```
TypeError: string indices must be integers, not 'str':
pilot/helpers/agents/Developer.py:132
pilot/helpers/agents/Developer.py:92
pilot/helpers/Project.py:204
```

Exact code number varies by version but it's always here:

https://github.com/Pythagora-io/gpt-pilot/blob/e31c67667dfad32a3c83a6425b86b122b488245e/pilot/helpers/agents/Developer.py#L132



Code after:

        Args:
            prompt_path: The path to a prompt.
            prompt_data: Data associated with the prompt.
            function_calls: Optional function calls to be included in the message.
            should_log_message: Flag if final response should be logged.
        Returns:
            The response from the agent.
        """
        # craft message
        self.construct_and_add_message_from_prompt(prompt_path, prompt_data)

        # TODO: move this if block (and the other below) to Developer agent - https://github.com/Pythagora-io/gpt-pilot/issues/91#issuecomment-1751964079
        # check if we already have the LLM response saved
        if hasattr(self.agent, 'save_dev_steps') and self.agent.save_dev_steps:
            self.agent.project.llm_req_num += 1

        self.agent.project.finish_loading()

        try:
            self.replace_files()
            response = create_gpt_chat_completion(self.messages, self.high_level_step, self.agent.project,
                                                  function_calls=function_calls, prompt_data=prompt_data,
                                                  temperature=self.temperature)
        except TokenLimitError as e:
            save_development_step(self.agent.project, prompt_path, prompt_data, self.messages, {"text": ""}, str(e))
            raise e

        # TODO: move this code to Developer agent - https://github.com/Pythagora-io/gpt-pilot/issues/91#issuecomment-1751964079
        if hasattr(self.agent, 'save_dev_steps') and self.agent.save_dev_steps:
            save_development_step(self.agent.project, prompt_path, prompt_data, self.messages, response)

        # TODO handle errors from OpenAI
        # It's complicated because calling functions are expecting different types of responses - string or tuple
        # https://github.com/Pythagora-io/gpt-pilot/issues/165 & #91
        if response == {} or response is None:
            # This should never happen since we're raising ApiError in create_gpt_chat_completion
            # Leaving this in place in case there's a case where this can still happen
            logger.error('Aborting with "OpenAI API error happened"')
            print(color_red_bold('There was an error talking to OpenAI API. Please try again later.'))
            payload_size_kb = len(json.dumps(self.messages)) // 1000
            raise ApiError(f"Unknown API error (prompt: {prompt_path}, request size: {payload_size_kb}KB)")

        try:
            response = parse_agent_response(response, function_calls)
        except (KeyError, json.JSONDecodeError) as err:
            logger.error("Error while parsing LLM response: {err.__class__.__name__}: {err}")
            print(color_red_bold(f'There was an error parsing LLM response: \"{err.__class__.__name__}: {err}\". Please try again later.'))
            raise ApiError(f"Error parsing LLM response: {err.__class__.__name__}: {err}: Response text: {response}") from err

        message_content = self.format_message_content(response, function_calls)