gitweixin
  • 首页
  • 小程序代码
    • 资讯读书
    • 工具类
    • O2O
    • 地图定位
    • 社交
    • 行业软件
    • 电商类
    • 互联网类
    • 企业类
    • UI控件
  • 大数据开发
    • Hadoop
    • Spark
    • Hbase
    • Elasticsearch
    • Kafka
    • Flink
    • 数据仓库
    • 数据挖掘
    • flume
    • Kafka
    • Hive
    • shardingsphere
    • solr
  • 开发博客
    • Android
    • php
    • python
    • 运维
    • 技术架构
    • 数据库
  • 程序员网赚
  • bug清单
  • 量化投资
  • 在线查询工具
    • 去行号
    • 在线时间戳转换工具
    • 免费图片批量修改尺寸在线工具
    • SVG转JPG在线工具

年度归档2023

精品微信小程序开发门户,代码全部亲测可用

  • 首页   /  
  • 2023
  • ( 页面2 )
python 9月 11,2023

清华开源ChatGPT自动编程ChatDev项目roster.py代码解读

ChatDev开源项目中的Roster类,用于管理招募、查询和打印员工的信息。下面是对该类的解析和注释:

class Roster():
    def __init__(self) -> None:
        self.agents = list()  # 存储员工姓名的列表

    def _recruit(self, agent_name: str):
        self.agents.append(agent_name)  # 将新招募的员工姓名添加到列表中

    def _exist_employee(self, agent_name: str):
        names = self.agents + [agent_name]  # 创建包含已有员工姓名和待查询姓名的列表
        names = [name.lower().strip() for name in names]  # 将所有姓名转换为小写字母,并去除首尾空格
        names = [name.replace(" ", "").replace("_", "") for name in names]  # 去除姓名中的空格和下划线
        agent_name = names[-1]  # 待查询员工姓名
        if agent_name in names[:-1]:  # 如果待查询姓名在已有员工姓名列表中,则员工已存在
            return True
        return False

    def _print_employees(self):
        names = self.agents  # 获取已有员工姓名列表
        names = [name.lower().strip() for name in names]  # 将所有姓名转换为小写字母,并去除首尾空格
        print("Employees: {}".format(names))  # 打印员工姓名列表

该Roster类具有以下方法:

  • __init__(self): 类的初始化方法,将self.agents初始化为空列表。
  • _recruit(self, agent_name:str): 添加新招募的员工姓名到self.agents列表中。
  • _exist_employee(self, agent_name:str): 根据名字检查员工是否已存在。首先,将已有员工姓名和待查询员工姓名合并为一个列表,并将所有姓名转换为小写字母并去除首尾空格。然后,通过比较待查询员工姓名与已有员工姓名列表,判断员工是否已存在。
  • _print_employees(self): 打印已有员工的姓名列表。

这个类的主要作用是提供了一种管理员工信息的方式,可以招募新员工、检查员工是否已存在以及打印已有员工的姓名列表。

作者 east
chatgpt 9月 11,2023

清华开源ChatGPT自动编程ChatDev项目phase.py代码解读

首先,在__init__方法中,初始化了Phase类的一些属性,包括:

  • assistant_role_name:接收对话的角色名称
  • user_role_name:开始对话的角色名称
  • phase_prompt:本阶段的提示内容
  • role_prompts:所有角色的提示内容
  • phase_name:本阶段的名称
  • model_type:模型类型
  • log_filepath:日志文件路径

然后,在chatting方法中,定义了处理对话并生成阶段总结的逻辑。该方法的参数包括:

  • chat_env:全局对话环境,用于员工检测(可以忽略)
  • task_prompt:建立软件的用户查询提示
  • assistant_role_name:接收对话的角色名称
  • user_role_name:开始对话的角色名称
  • phase_prompt:本阶段的提示内容
  • phase_name:本阶段的名称
  • assistant_role_prompt:助手角色的提示内容
  • user_role_prompt:用户角色的提示内容
  • task_type:任务类型
  • need_reflect:是否需要进行自我反思
  • with_task_specify:是否具有任务说明
  • model_type:模型类型
  • placeholders:用于生成阶段提示的占位符
  • chat_turn_limit:每次对话的最大轮数

在该方法中,首先检查必要的参数,并初始化角色扮演的会话role_play_session。

接下来,开始对话,并处理多轮对话。通过调用role_play_session.step方法进行助手和用户之间的交互,并记录对话细节。

然后,根据对话的结果判断是否生成了阶段总结。如果助手或用户的回复消息具有特殊的”<INFO>”标记,则将该回复作为阶段总结。否则,将助手最后的回复消息作为阶段总结。

解析self_reflection方法的作用和注释:

  • 这个方法用于执行角色扮演会话的自省阶段。
  • 参数列表:
    • task_prompt:构建软件的用户查询提示
    • role_play_session:需要自省的角色扮演会话
    • phase_name:需要自省的聊天阶段的名称
    • chat_env:全局聊天环境
  • 返回值:
    • reflected_content:字符串,自省结果
  • 方法内部逻辑:
    • 首先,通过比较用户和助手存储的消息数量,确定要用于自省的消息列表。
    • 然后,将消息列表中的每条消息格式化为 “角色名: 内容” 的形式,并使用换行符连接起来。
    • 根据不同的聊天阶段名称,设置相应的问题。
    • 调用 self.chatting 方法执行聊天,得到自省结果。
    • 最后,根据阶段名称返回相应的结果,如果是 “recruiting” 阶段,则根据结果回答 “Yes” 或 “No”。

接下来是两个抽象方法 update_phase_env 和 update_chat_env,这两个方法在这里只是简单的定义了函数体,需要在自定义的聊天阶段中实现具体逻辑。

最后是 execute 方法:

  • 这个方法用于执行聊天阶段。
  • 参数列表:
    • chat_env:全局聊天环境
    • chat_turn_limit:每个聊天中的轮次限制
    • need_reflect:是否需要自省的标志
  • 返回值:
    • chat_env:使用本阶段执行结果更新的全局聊天环境
  • 方法内部逻辑:
    • 调用 update_phase_env 方法更新阶段环境。
    • 调用 self.chatting 方法执行聊天,并将结果保存到 self.seminar_conclusion。
    • 调用 update_chat_env 方法更新全局聊天环境。
    • 返回更新后的全局聊天环境。

首先是DemandAnalysis类:

  • 这个类继承自 Phase 类,并实现了 update_phase_env 和 update_chat_env 两个抽象方法。
  • 在 update_chat_env 方法中,如果 self.seminar_conclusion 非空,则从中提取出 <INFO> 标记后的内容,并将其转换为小写形式并去除句号和空格。然后将该内容保存到 chat_env 的环境字典中的 modality 键对应的值中。
  • 最后,返回更新后的 chat_env。

接下来是LanguageChoose类:

  • 这个类同样继承自 Phase 类,并实现了 update_phase_env 和 update_chat_env 两个抽象方法。
  • 在 update_phase_env 方法中,使用 chat_env 的环境字典中的一些值更新 self.phase_env 字典。
  • 在 update_chat_env 方法中,首先判断 self.seminar_conclusion 是否非空,且其中是否包含 <INFO> 标记。
    • 如果是,则从 self.seminar_conclusion 中提取出 <INFO> 标记后的内容,并将其转换为小写形式并去除句号和空格。然后将该内容保存到 chat_env 的环境字典中的 language 键对应的值中。
    • 如果不是,则判断 self.seminar_conclusion 是否非空,如果是,则直接将其保存到 chat_env 的环境字典中的 language 键对应的值中。
    • 如果都不满足上述条件,则将 language 设置为 “Python”。
  • 最后,返回更新后的 chat_env。

然后是Coding类和ArtDesign类,它们的实现逻辑与上述两个类类似,都继承自 Phase 类,并分别实现了 update_phase_env 和 update_chat_env 两个抽象方法。这里略过具体内容的解释,可根据注释进行理解。

这些阶段类都用于更新聊天环境的阶段信息,从用户和助手的对话中提取有关任务、语言选择、代码和艺术设计等方面的信息,并将其保存到聊天环境的环境字典中。

  • ArtIntegration阶段类:该类用于将chat_env中的环境信息更新到phase_env中,主要更新了任务、语言、代码以及图像等信息。
  • CodeComplete阶段类:该类也是将chat_env中的环境信息更新到phase_env中,更新了任务、模态、观点、语言、代码以及未实现的文件等信息。其中,还检查是否有未实现的文件,如果有且尝试次数未超过最大次数,则记录下该文件名,并更新计数器。
  • CodeReviewComment阶段类:同样将chat_env中的环境信息更新到phase_env中,更新了任务、模态、观点、语言、代码以及图像等信息。
  • CodeReviewModification阶段类:将chat_env中的环境信息更新到phase_env中,更新了任务、模态、观点、语言、代码以及评论等信息。如果评论中包含代码块标识”“`”,则更新chat_env中的代码,并重新生成代码。
  • CodeReviewHuman阶段类:该阶段需要人工介入,通过输入提供反馈意见。将chat_env中的环境信息更新到phase_env中,更新了任务、模态、观点、语言、代码以及评论等信息。

这部分代码是ChatDev开源项目中的一些阶段(Phase)类。每个阶段类都是一个用于处理对话环境(chat_env)的特定阶段,它们分别为TestErrorSummary、TestModification、EnvironmentDoc和Manual。

首先,让我们逐个解析每个阶段类的作用和注释:

  1. TestErrorSummary:
    • 作用:处理软件测试中的错误摘要阶段,生成测试报告,并根据报告中的错误信息执行相应的修复操作。
    • 添加注释:
      • update_phase_env():更新阶段环境,将测试报告、任务提示、模态、构想、编程语言和源代码等信息存储在阶段环境中。
      • update_chat_env():更新对话环境,将错误摘要和测试报告存储在对话环境的环境字典中。
      • execute():执行阶段任务,更新阶段环境和对话环境,并返回更新后的对话环境。
  2. TestModification:
    • 作用:处理软件修改阶段,根据讨论的结果,更新源代码,并生成新的代码版本。
    • 添加注释:
      • update_phase_env():更新阶段环境,将任务提示、模态、构想、编程语言、测试报告、错误摘要和源代码等信息存储在阶段环境中。
      • update_chat_env():更新对话环境,根据讨论结果更新源代码,并重新生成代码。
  3. EnvironmentDoc:
    • 作用:处理环境文档阶段,更新项目的需求文档,并根据讨论结果生成新的文档版本。
    • 添加注释:
      • update_phase_env():更新阶段环境,将任务提示、模态、构想、编程语言和源代码等信息存储在阶段环境中。
      • update_chat_env():更新对话环境,根据讨论结果更新需求文档,并重新生成文档。
  4. Manual:
    • 作用:处理操作手册阶段,更新项目的操作手册,并根据讨论结果生成新的手册版本。
    • 添加注释:
      • update_phase_env():更新阶段环境,将任务提示、模态、构想、编程语言、源代码和需求文档等信息存储在阶段环境中。
      • update_chat_env():更新对话环境,根据讨论结果更新操作手册,并重新生成手册。

每个阶段类都实现了update_phase_env()和update_chat_env()方法来更新阶段环境和对话环境。同时,它们还实现了execute()方法来执行特定的阶段任务并返回更新后的对话环境。

源代码如下:

import os
import re
from abc import ABC, abstractmethod

from camel.agents import RolePlaying
from camel.messages import ChatMessage
from camel.typing import TaskType, ModelType
from chatdev.chat_env import ChatEnv
from chatdev.statistics import get_info
from chatdev.utils import log_and_print_online, log_arguments


class Phase(ABC):

    def __init__(self,
                 assistant_role_name,
                 user_role_name,
                 phase_prompt,
                 role_prompts,
                 phase_name,
                 model_type,
                 log_filepath):
        """

        Args:
            assistant_role_name: who receives chat in a phase
            user_role_name: who starts the chat in a phase
            phase_prompt: prompt of this phase
            role_prompts: prompts of all roles
            phase_name: name of this phase
        """
        self.seminar_conclusion = None
        self.assistant_role_name = assistant_role_name
        self.user_role_name = user_role_name
        self.phase_prompt = phase_prompt
        self.phase_env = dict()
        self.phase_name = phase_name
        self.assistant_role_prompt = role_prompts[assistant_role_name]
        self.user_role_prompt = role_prompts[user_role_name]
        self.ceo_prompt = role_prompts["Chief Executive Officer"]
        self.counselor_prompt = role_prompts["Counselor"]
        self.timeout_seconds = 1.0
        self.max_retries = 3
        self.reflection_prompt = """Here is a conversation between two roles: {conversations} {question}"""
        self.model_type = model_type
        self.log_filepath = log_filepath

    @log_arguments
    def chatting(
            self,
            chat_env,
            task_prompt: str,
            assistant_role_name: str,
            user_role_name: str,
            phase_prompt: str,
            phase_name: str,
            assistant_role_prompt: str,
            user_role_prompt: str,
            task_type=TaskType.CHATDEV,
            need_reflect=False,
            with_task_specify=False,
            model_type=ModelType.GPT_3_5_TURBO,
            placeholders=None,
            chat_turn_limit=10
    ) -> str:
        """

        Args:
            chat_env: global chatchain environment TODO: only for employee detection, can be deleted
            task_prompt: user query prompt for building the software
            assistant_role_name: who receives the chat
            user_role_name: who starts the chat
            phase_prompt: prompt of the phase
            phase_name: name of the phase
            assistant_role_prompt: prompt of assistant role
            user_role_prompt: prompt of user role
            task_type: task type
            need_reflect: flag for checking reflection
            with_task_specify: with task specify
            model_type: model type
            placeholders: placeholders for phase environment to generate phase prompt
            chat_turn_limit: turn limits in each chat

        Returns:

        """

        if placeholders is None:
            placeholders = {}
        assert 1 <= chat_turn_limit <= 100

        if not chat_env.exist_employee(assistant_role_name):
            raise ValueError(f"{assistant_role_name} not recruited in ChatEnv.")
        if not chat_env.exist_employee(user_role_name):
            raise ValueError(f"{user_role_name} not recruited in ChatEnv.")

        # init role play
        role_play_session = RolePlaying(
            assistant_role_name=assistant_role_name,
            user_role_name=user_role_name,
            assistant_role_prompt=assistant_role_prompt,
            user_role_prompt=user_role_prompt,
            task_prompt=task_prompt,
            task_type=task_type,
            with_task_specify=with_task_specify,
            model_type=model_type,
        )

        # log_and_print_online("System", role_play_session.assistant_sys_msg)
        # log_and_print_online("System", role_play_session.user_sys_msg)

        # start the chat
        _, input_user_msg = role_play_session.init_chat(None, placeholders, phase_prompt)
        seminar_conclusion = None

        # handle chats
        # the purpose of the chatting in one phase is to get a seminar conclusion
        # there are two types of conclusion
        # 1. with "<INFO>" mark
        # 1.1 get seminar conclusion flag (ChatAgent.info) from assistant or user role, which means there exist special "<INFO>" mark in the conversation
        # 1.2 add "<INFO>" to the reflected content of the chat (which may be terminated chat without "<INFO>" mark)
        # 2. without "<INFO>" mark, which means the chat is terminated or normally ended without generating a marked conclusion, and there is no need to reflect
        for i in range(chat_turn_limit):
            # start the chat, we represent the user and send msg to assistant
            # 1. so the input_user_msg should be assistant_role_prompt + phase_prompt
            # 2. then input_user_msg send to LLM and get assistant_response
            # 3. now we represent the assistant and send msg to user, so the input_assistant_msg is user_role_prompt + assistant_response
            # 4. then input_assistant_msg send to LLM and get user_response
            # all above are done in role_play_session.step, which contains two interactions with LLM
            # the first interaction is logged in role_play_session.init_chat
            assistant_response, user_response = role_play_session.step(input_user_msg, chat_turn_limit == 1)

            conversation_meta = "**" + assistant_role_name + "<->" + user_role_name + " on : " + str(
                phase_name) + ", turn " + str(i) + "**\n\n"

            # TODO: max_tokens_exceeded errors here
            if isinstance(assistant_response.msg, ChatMessage):
                # we log the second interaction here
                log_and_print_online(role_play_session.assistant_agent.role_name,
                                     conversation_meta + "[" + role_play_session.user_agent.system_message.content + "]\n\n" + assistant_response.msg.content)
                if role_play_session.assistant_agent.info:
                    seminar_conclusion = assistant_response.msg.content
                    break
                if assistant_response.terminated:
                    break

            if isinstance(user_response.msg, ChatMessage):
                # here is the result of the second interaction, which may be used to start the next chat turn
                log_and_print_online(role_play_session.user_agent.role_name,
                                     conversation_meta + "[" + role_play_session.assistant_agent.system_message.content + "]\n\n" + user_response.msg.content)
                if role_play_session.user_agent.info:
                    seminar_conclusion = user_response.msg.content
                    break
                if user_response.terminated:
                    break

            # continue the chat
            if chat_turn_limit > 1 and isinstance(user_response.msg, ChatMessage):
                input_user_msg = user_response.msg
            else:
                break

        # conduct self reflection
        if need_reflect:
            if seminar_conclusion in [None, ""]:
                seminar_conclusion = "<INFO> " + self.self_reflection(task_prompt, role_play_session, phase_name,
                                                                      chat_env)
            if "recruiting" in phase_name:
                if "Yes".lower() not in seminar_conclusion.lower() and "No".lower() not in seminar_conclusion.lower():
                    seminar_conclusion = "<INFO> " + self.self_reflection(task_prompt, role_play_session,
                                                                          phase_name,
                                                                          chat_env)
            elif seminar_conclusion in [None, ""]:
                seminar_conclusion = "<INFO> " + self.self_reflection(task_prompt, role_play_session, phase_name,
                                                                      chat_env)
        else:
            seminar_conclusion = assistant_response.msg.content

        log_and_print_online("**[Seminar Conclusion]**:\n\n {}".format(seminar_conclusion))
        seminar_conclusion = seminar_conclusion.split("<INFO>")[-1]
        return seminar_conclusion

    def self_reflection(self,
                        task_prompt: str,
                        role_play_session: RolePlaying,
                        phase_name: str,
                        chat_env: ChatEnv) -> str:
        """

        Args:
            task_prompt: user query prompt for building the software
            role_play_session: role play session from the chat phase which needs reflection
            phase_name: name of the chat phase which needs reflection
            chat_env: global chatchain environment

        Returns:
            reflected_content: str, reflected results

        """
        messages = role_play_session.assistant_agent.stored_messages if len(
            role_play_session.assistant_agent.stored_messages) >= len(
            role_play_session.user_agent.stored_messages) else role_play_session.user_agent.stored_messages
        messages = ["{}: {}".format(message.role_name, message.content.replace("\n\n", "\n")) for message in messages]
        messages = "\n\n".join(messages)

        if "recruiting" in phase_name:
            question = """Answer their final discussed conclusion (Yes or No) in the discussion without any other words, e.g., "Yes" """
        elif phase_name == "DemandAnalysis":
            question = """Answer their final product modality in the discussion without any other words, e.g., "PowerPoint" """
        # elif phase_name in [PhaseType.BRAINSTORMING]:
        #     question = """Conclude three most creative and imaginative brainstorm ideas from the whole discussion, in the format: "1) *; 2) *; 3) *; where '*' represents a suggestion." """
        elif phase_name == "LanguageChoose":
            question = """Conclude the programming language being discussed for software development, in the format: "*" where '*' represents a programming language." """
        elif phase_name == "EnvironmentDoc":
            question = """According to the codes and file format listed above, write a requirements.txt file to specify the dependencies or packages required for the project to run properly." """
        else:
            raise ValueError(f"Reflection of phase {phase_name}: Not Assigned.")

        # Reflections actually is a special phase between CEO and counselor
        # They read the whole chatting history of this phase and give refined conclusion of this phase
        reflected_content = \
            self.chatting(chat_env=chat_env,
                          task_prompt=task_prompt,
                          assistant_role_name="Chief Executive Officer",
                          user_role_name="Counselor",
                          phase_prompt=self.reflection_prompt,
                          phase_name="Reflection",
                          assistant_role_prompt=self.ceo_prompt,
                          user_role_prompt=self.counselor_prompt,
                          placeholders={"conversations": messages, "question": question},
                          need_reflect=False,
                          chat_turn_limit=1,
                          model_type=self.model_type)

        if "recruiting" in phase_name:
            if "Yes".lower() in reflected_content.lower():
                return "Yes"
            return "No"
        else:
            return reflected_content

    @abstractmethod
    def update_phase_env(self, chat_env):
        """
        update self.phase_env (if needed) using chat_env, then the chatting will use self.phase_env to follow the context and fill placeholders in phase prompt
        must be implemented in customized phase
        the usual format is just like:
        ```
            self.phase_env.update({key:chat_env[key]})
        ```
        Args:
            chat_env: global chat chain environment

        Returns: None

        """
        pass

    @abstractmethod
    def update_chat_env(self, chat_env) -> ChatEnv:
        """
        update chan_env based on the results of self.execute, which is self.seminar_conclusion
        must be implemented in customized phase
        the usual format is just like:
        ```
            chat_env.xxx = some_func_for_postprocess(self.seminar_conclusion)
        ```
        Args:
            chat_env:global chat chain environment

        Returns:
            chat_env: updated global chat chain environment

        """
        pass

    def execute(self, chat_env, chat_turn_limit, need_reflect) -> ChatEnv:
        """
        execute the chatting in this phase
        1. receive information from environment: update the phase environment from global environment
        2. execute the chatting
        3. change the environment: update the global environment using the conclusion
        Args:
            chat_env: global chat chain environment
            chat_turn_limit: turn limit in each chat
            need_reflect: flag for reflection

        Returns:
            chat_env: updated global chat chain environment using the conclusion from this phase execution

        """
        self.update_phase_env(chat_env)
        self.seminar_conclusion = \
            self.chatting(chat_env=chat_env,
                          task_prompt=chat_env.env_dict['task_prompt'],
                          need_reflect=need_reflect,
                          assistant_role_name=self.assistant_role_name,
                          user_role_name=self.user_role_name,
                          phase_prompt=self.phase_prompt,
                          phase_name=self.phase_name,
                          assistant_role_prompt=self.assistant_role_prompt,
                          user_role_prompt=self.user_role_prompt,
                          chat_turn_limit=chat_turn_limit,
                          placeholders=self.phase_env,
                          model_type=self.model_type)
        chat_env = self.update_chat_env(chat_env)
        return chat_env


class DemandAnalysis(Phase):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)

    def update_phase_env(self, chat_env):
        pass

    def update_chat_env(self, chat_env) -> ChatEnv:
        if len(self.seminar_conclusion) > 0:
            chat_env.env_dict['modality'] = self.seminar_conclusion.split("<INFO>")[-1].lower().replace(".", "").strip()
        return chat_env


class LanguageChoose(Phase):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)

    def update_phase_env(self, chat_env):
        self.phase_env.update({"task": chat_env.env_dict['task_prompt'],
                               "modality": chat_env.env_dict['modality'],
                               "ideas": chat_env.env_dict['ideas']})

    def update_chat_env(self, chat_env) -> ChatEnv:
        if len(self.seminar_conclusion) > 0 and "<INFO>" in self.seminar_conclusion:
            chat_env.env_dict['language'] = self.seminar_conclusion.split("<INFO>")[-1].lower().replace(".", "").strip()
        elif len(self.seminar_conclusion) > 0:
            chat_env.env_dict['language'] = self.seminar_conclusion
        else:
            chat_env.env_dict['language'] = "Python"
        return chat_env


class Coding(Phase):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)

    def update_phase_env(self, chat_env):
        gui = "" if not chat_env.config.gui_design \
            else "The software should be equipped with graphical user interface (GUI) so that user can visually and graphically use it; so you must choose a GUI framework (e.g., in Python, you can implement GUI via tkinter, Pygame, Flexx, PyGUI, etc,)."
        self.phase_env.update({"task": chat_env.env_dict['task_prompt'],
                               "modality": chat_env.env_dict['modality'],
                               "ideas": chat_env.env_dict['ideas'],
                               "language": chat_env.env_dict['language'],
                               "gui": gui})

    def update_chat_env(self, chat_env) -> ChatEnv:
        chat_env.update_codes(self.seminar_conclusion)
        if len(chat_env.codes.codebooks.keys()) == 0:
            raise ValueError("No Valid Codes.")
        chat_env.rewrite_codes()
        log_and_print_online("**[Software Info]**:\n\n {}".format(get_info(chat_env.env_dict['directory'],self.log_filepath)))
        return chat_env


class ArtDesign(Phase):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)

    def update_phase_env(self, chat_env):
        self.phase_env = {"task": chat_env.env_dict['task_prompt'],
                          "language": chat_env.env_dict['language'],
                          "codes": chat_env.get_codes()}

    def update_chat_env(self, chat_env) -> ChatEnv:
        chat_env.proposed_images = chat_env.get_proposed_images_from_message(self.seminar_conclusion)
        log_and_print_online("**[Software Info]**:\n\n {}".format(get_info(chat_env.env_dict['directory'],self.log_filepath)))
        return chat_env


class ArtIntegration(Phase):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)

    def update_phase_env(self, chat_env):
        self.phase_env = {"task": chat_env.env_dict['task_prompt'],
                          "language": chat_env.env_dict['language'],
                          "codes": chat_env.get_codes(),
                          "images": "\n".join(
                              ["{}: {}".format(filename, chat_env.proposed_images[filename]) for
                               filename in sorted(list(chat_env.proposed_images.keys()))])}

    def update_chat_env(self, chat_env) -> ChatEnv:
        chat_env.update_codes(self.seminar_conclusion)
        chat_env.rewrite_codes()
        # chat_env.generate_images_from_codes()
        log_and_print_online("**[Software Info]**:\n\n {}".format(get_info(chat_env.env_dict['directory'],self.log_filepath)))
        return chat_env


class CodeComplete(Phase):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)

    def update_phase_env(self, chat_env):
        self.phase_env.update({"task": chat_env.env_dict['task_prompt'],
                               "modality": chat_env.env_dict['modality'],
                               "ideas": chat_env.env_dict['ideas'],
                               "language": chat_env.env_dict['language'],
                               "codes": chat_env.get_codes(),
                               "unimplemented_file": ""})
        unimplemented_file = ""
        for filename in self.phase_env['pyfiles']:
            code_content = open(os.path.join(chat_env.env_dict['directory'], filename)).read()
            lines = [line.strip() for line in code_content.split("\n") if line.strip() == "pass"]
            if len(lines) > 0 and self.phase_env['num_tried'][filename] < self.phase_env['max_num_implement']:
                unimplemented_file = filename
                break
        self.phase_env['num_tried'][unimplemented_file] += 1
        self.phase_env['unimplemented_file'] = unimplemented_file

    def update_chat_env(self, chat_env) -> ChatEnv:
        chat_env.update_codes(self.seminar_conclusion)
        if len(chat_env.codes.codebooks.keys()) == 0:
            raise ValueError("No Valid Codes.")
        chat_env.rewrite_codes()
        log_and_print_online("**[Software Info]**:\n\n {}".format(get_info(chat_env.env_dict['directory'],self.log_filepath)))
        return chat_env


class CodeReviewComment(Phase):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)

    def update_phase_env(self, chat_env):
        self.phase_env.update(
            {"task": chat_env.env_dict['task_prompt'],
             "modality": chat_env.env_dict['modality'],
             "ideas": chat_env.env_dict['ideas'],
             "language": chat_env.env_dict['language'],
             "codes": chat_env.get_codes(),
             "images": ", ".join(chat_env.incorporated_images)})

    def update_chat_env(self, chat_env) -> ChatEnv:
        chat_env.env_dict['review_comments'] = self.seminar_conclusion
        return chat_env


class CodeReviewModification(Phase):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)

    def update_phase_env(self, chat_env):
        self.phase_env.update({"task": chat_env.env_dict['task_prompt'],
                               "modality": chat_env.env_dict['modality'],
                               "ideas": chat_env.env_dict['ideas'],
                               "language": chat_env.env_dict['language'],
                               "codes": chat_env.get_codes(),
                               "comments": chat_env.env_dict['review_comments']})

    def update_chat_env(self, chat_env) -> ChatEnv:
        if "```".lower() in self.seminar_conclusion.lower():
            chat_env.update_codes(self.seminar_conclusion)
            chat_env.rewrite_codes()
            log_and_print_online("**[Software Info]**:\n\n {}".format(get_info(chat_env.env_dict['directory'],self.log_filepath)))
        self.phase_env['modification_conclusion'] = self.seminar_conclusion
        return chat_env


class CodeReviewHuman(Phase):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)

    def update_phase_env(self, chat_env):
        print(
            f"You can participate in the development of the software {chat_env.env_dict['task_prompt']}. Please input your feedback. (\"End\" to quit the involvement.)")
        provided_comments = input()
        self.phase_env.update({"task": chat_env.env_dict['task_prompt'],
                               "modality": chat_env.env_dict['modality'],
                               "ideas": chat_env.env_dict['ideas'],
                               "language": chat_env.env_dict['language'],
                               "codes": chat_env.get_codes(),
                               "comments": provided_comments})

    def update_chat_env(self, chat_env) -> ChatEnv:
        if "```".lower() in self.seminar_conclusion.lower():
            chat_env.update_codes(self.seminar_conclusion)
            chat_env.rewrite_codes()
            log_and_print_online("**[Software Info]**:\n\n {}".format(get_info(chat_env.env_dict['directory'],self.log_filepath)))
        return chat_env


class TestErrorSummary(Phase):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)

    def update_phase_env(self, chat_env):
        chat_env.generate_images_from_codes()
        (exist_bugs_flag, test_reports) = chat_env.exist_bugs()
        self.phase_env.update({"task": chat_env.env_dict['task_prompt'],
                               "modality": chat_env.env_dict['modality'],
                               "ideas": chat_env.env_dict['ideas'],
                               "language": chat_env.env_dict['language'],
                               "codes": chat_env.get_codes(),
                               "test_reports": test_reports,
                               "exist_bugs_flag": exist_bugs_flag})
        log_and_print_online("**[Test Reports]**:\n\n{}".format(test_reports))

    def update_chat_env(self, chat_env) -> ChatEnv:
        chat_env.env_dict['error_summary'] = self.seminar_conclusion
        chat_env.env_dict['test_reports'] = self.phase_env['test_reports']

        return chat_env

    def execute(self, chat_env, chat_turn_limit, need_reflect) -> ChatEnv:
        self.update_phase_env(chat_env)
        if "ModuleNotFoundError" in self.phase_env['test_reports']:
            chat_env.fix_module_not_found_error(self.phase_env['test_reports'])
            log_and_print_online(
                f"Software Test Engineer found ModuleNotFoundError:\n{self.phase_env['test_reports']}\n")
            pip_install_content = ""
            for match in re.finditer(r"No module named '(\S+)'", self.phase_env['test_reports'], re.DOTALL):
                module = match.group(1)
                pip_install_content += "{}\n```{}\n{}\n```\n".format("cmd", "bash", f"pip install {module}")
                log_and_print_online(f"Programmer resolve ModuleNotFoundError by:\n{pip_install_content}\n")
            self.seminar_conclusion = "nothing need to do"
        else:
            self.seminar_conclusion = \
                self.chatting(chat_env=chat_env,
                              task_prompt=chat_env.env_dict['task_prompt'],
                              need_reflect=need_reflect,
                              assistant_role_name=self.assistant_role_name,
                              user_role_name=self.user_role_name,
                              phase_prompt=self.phase_prompt,
                              phase_name=self.phase_name,
                              assistant_role_prompt=self.assistant_role_prompt,
                              user_role_prompt=self.user_role_prompt,
                              chat_turn_limit=chat_turn_limit,
                              placeholders=self.phase_env)
        chat_env = self.update_chat_env(chat_env)
        return chat_env


class TestModification(Phase):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)

    def update_phase_env(self, chat_env):
        self.phase_env.update({"task": chat_env.env_dict['task_prompt'],
                               "modality": chat_env.env_dict['modality'],
                               "ideas": chat_env.env_dict['ideas'],
                               "language": chat_env.env_dict['language'],
                               "test_reports": chat_env.env_dict['test_reports'],
                               "error_summary": chat_env.env_dict['error_summary'],
                               "codes": chat_env.get_codes()
                               })

    def update_chat_env(self, chat_env) -> ChatEnv:
        if "```".lower() in self.seminar_conclusion.lower():
            chat_env.update_codes(self.seminar_conclusion)
            chat_env.rewrite_codes()
            log_and_print_online("**[Software Info]**:\n\n {}".format(get_info(chat_env.env_dict['directory'],self.log_filepath)))
        return chat_env


class EnvironmentDoc(Phase):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)

    def update_phase_env(self, chat_env):
        self.phase_env.update({"task": chat_env.env_dict['task_prompt'],
                               "modality": chat_env.env_dict['modality'],
                               "ideas": chat_env.env_dict['ideas'],
                               "language": chat_env.env_dict['language'],
                               "codes": chat_env.get_codes()})

    def update_chat_env(self, chat_env) -> ChatEnv:
        chat_env._update_requirements(self.seminar_conclusion)
        chat_env.rewrite_requirements()
        log_and_print_online("**[Software Info]**:\n\n {}".format(get_info(chat_env.env_dict['directory'],self.log_filepath)))
        return chat_env


class Manual(Phase):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)

    def update_phase_env(self, chat_env):
        self.phase_env.update({"task": chat_env.env_dict['task_prompt'],
                               "modality": chat_env.env_dict['modality'],
                               "ideas": chat_env.env_dict['ideas'],
                               "language": chat_env.env_dict['language'],
                               "codes": chat_env.get_codes(),
                               "requirements": chat_env.get_requirements()})

    def update_chat_env(self, chat_env) -> ChatEnv:
        chat_env._update_manuals(self.seminar_conclusion)
        chat_env.rewrite_manuals()
        return chat_env
作者 east
python 9月 11,2023

清华开源ChatGPT自动编程ChatDev项目documents.py代码解读

以下是对”documents.py“代码的详细解析和添加注释:

import re
import os
import time
from colorama import Fore

class Documents():
    def __init__(self, generated_content="", parse=True, predifined_filename=None):
        self.directory: str = None  # 存放文档的目录路径
        self.generated_content = generated_content  # 生成的内容
        self.docbooks = {}  # 存放文档的字典,键为文件名,值为文档内容

        if generated_content != "":
            if parse:
                regex = r"```\n(.*?)```"  # 用于匹配文档内容的正则表达式模式
                matches = re.finditer(regex, self.generated_content, re.DOTALL)
                for match in matches:
                    filename = "requirements.txt"  # 默认的文件名
                    doc = match.group(1)  # 匹配到的文档内容
                    self.docbooks[filename] = doc  # 将文档内容存入字典
            else:
                self.docbooks[predifined_filename] = self.generated_content  # 将生成的内容存入字典,使用预定义的文件名

    def _update_docs(self, generated_content, parse=True, predifined_filename=""):
        new_docs = Documents(generated_content, parse, predifined_filename)  # 创建一个新的Documents对象
        for key in new_docs.docbooks.keys():
            if key not in self.docbooks.keys() or self.docbooks[key] != new_docs.docbooks[key]:
                print("{} updated.".format(key))  # 打印更新的文件名
                print(Fore.WHITE + "------Old:\n{}\n------New:\n{}".format(
                    self.docbooks[key] if key in self.docbooks.keys() else "# None", new_docs.docbooks[key]))  # 打印旧内容和新内容
                self.docbooks[key] = new_docs.docbooks[key]  # 更新文档内容

    def _rewrite_docs(self):
        directory = self.directory
        if not os.path.exists(directory):  # 如果目录不存在,则创建目录
            os.mkdir(directory)
            print("{} Created.".format(directory))
        for filename in self.docbooks.keys():
            with open(os.path.join(directory, filename), "w", encoding="utf-8") as writer:  # 将文档内容写入文件
                writer.write(self.docbooks[filename])
                print(os.path.join(directory, filename), "Writed")

    def _get_docs(self):
        content = ""
        for filename in self.docbooks.keys():  # 将所有文档内容拼接成字符串
            content += "{}\n```\n{}\n```\n\n".format(filename, self.docbooks[filename])
        return content  # 返回文档内容的字符串表示

代码主要定义了一个Documents类,用于管理生成的文档。其中包含以下方法:

  • __init__(self, generated_content="", parse=True, predifined_filename=None):类的构造方法,初始化Documents对象。
  • _update_docs(self, generated_content, parse=True, predifined_filename=""):更新文档内容。
  • _rewrite_docs(self):将文档内容写入文件。
  • _get_docs(self):返回文档内容的字符串表示。

该类的功能是根据生成的内容,管理和处理文档的保存、更新和获取操作。

作者 east
python 9月 11,2023

清华开源ChatGPT自动编程ChatDev项目composed_phase.py代码解读

定义了 ComposedPhase 类和相关函数。

check_bool 函数用于判断字符串是否为 True。它接收一个参数 s,并将其转换为小写后与字符串 “true” 进行比较。

ComposedPhase 是一个抽象基类,定义了复合阶段的基本结构和函数,不能被实例化。具体的复合阶段需要实现 update_phase_env 和 update_chat_env 这两个抽象函数。

在 __init__ 函数中,定义了复合阶段的具体属性和实例化过程:

  1. phase_name 表示复合阶段的名称,cycle_num 表示循环次数,composition 表示由哪些 SimplePhases 构成复合阶段,config_phase 和 config_role 表示 SimplePhases 和 Role 的配置信息。
  2. model_type 表示使用哪种模型类型,log_filepath 表示日志文件的路径。
  3. self.phase_env 是一个字典,用于存储阶段环境信息。
  4. 初始化 chat_turn_limit_default 表示每轮对话的最大回合数。
  5. self.role_prompts 是一个字典,表示各个角色的提示信息。
  6. 实例化所有 SimplePhases 并保存到 self.phases 字典中。每个 SimplePhase 都会接收一些参数,包括助手和用户的角色名称、阶段提示信息等。

update_phase_env 和 update_chat_env 是抽象方法,需要在具体阶段中实现。update_phase_env 接收一个 chat_env 字典作为参数,用于更新当前阶段的环境变量;update_chat_env 会根据当前阶段的结果更新全局 chat_env 字典,并返回更新后的 chat_env。这两个函数属于具体阶段的实现范畴,必须在子类中实现具体逻辑。

  1. @abstractmethod:
    • 这是一个装饰器,用于标识一个方法为抽象方法。在该代码段中,break_cycle方法被标记为抽象方法,表示它在抽象类ComposedPhase中没有具体的实现,而是需要在子类中进行实现。
  2. break_cycle(self, phase_env) -> bool:
    • 这是一个抽象方法,用于在ComposedPhase类中定义特殊条件来提前终止循环。
    • 参数phase_env是阶段环境。
    • 返回值是一个布尔类型,表示是否要提前结束循环。
  3. execute(self, chat_env) -> ChatEnv:
    • 这个方法执行ComposedPhase的逻辑。
    • 参数chat_env是全局聊天环境。
    • 返回值是ChatEnv类型的对象,表示更新后的全局聊天环境。
  4. self.update_phase_env(chat_env):
    • 这个方法用于从全局环境中更新阶段环境。
  5. for cycle_index in range(self.cycle_num):
    • 这是一个循环,用于控制执行的循环次数,循环次数由self.cycle_num确定。
  6. for phase_item in self.composition:
    • 这是一个循环,用于遍历ComposedPhase中组成阶段的每个SimplePhase。
  7. assert phase_item[“phaseType”] == “SimplePhase”:
    • 这是一个断言语句,用于确保phase_item的类型是”SimplePhase”,即简单阶段。
  8. phase = phase_item[‘phase’]
    • 获取phase_item字典中的”phase”键对应的值,表示SimplePhase的名称。
  9. max_turn_step = phase_item[‘max_turn_step’]
    • 获取phase_item字典中的”max_turn_step”键对应的值,表示最大的对话轮数。
  10. need_reflect = check_bool(phase_item[‘need_reflect’])
    • 获取phase_item字典中的”need_reflect”键对应的值,并通过check_bool函数将其转换为布尔类型,表示是否需要反射。
  11. self.phases[phase].phase_env = self.phase_env
    • 将当前ComposedPhase的阶段环境赋值给对应的SimplePhase的阶段环境。
  12. self.phases[phase].update_phase_env(chat_env)
    • 调用对应的SimplePhase的update_phase_env方法,更新SimplePhase的阶段环境。
  13. if self.break_cycle(self.phases[phase].phase_env): return chat_env
    • 调用break_cycle方法判断是否需要提前终止循环,如果需要则直接返回chat_env。
  14. chat_env = self.phases[phase].execute(chat_env, self.chat_turn_limit_default if max_turn_step <= 0 else max_turn_step, need_reflect)
    • 调用对应的SimplePhase的execute方法执行聊天,并更新chat_env。
  15. if self.break_cycle(self.phases[phase].phase_env): return chat_env
    • 再次调用break_cycle方法判断是否需要提前终止循环,如果需要则直接返回chat_env。
  16. else: print(f”Phase ‘{phase}’ is not yet implemented.
    Please write its config in phaseConfig.json
    and implement it in chatdev.phase”)
  • 如果SimplePhase未实现,则打印一条错误信息。
  1. chat_env = self.update_chat_env(chat_env)
    • 调用update_chat_env方法,更新全局聊天环境。
  2. return chat_env
    • 返回更新后的全局聊天环境。

下面是几个具体子类(Art、CodeCompleteAll、CodeReview和Test)的定义,它们都继承自ComposedPhase,并对其中的一些方法进行了具体实现。

  1. Art类:
    • update_phase_env方法为空实现。
    • update_chat_env方法返回chat_env。
    • break_cycle方法始终返回False。
  2. CodeCompleteAll类:
    • update_phase_env方法获取目录中所有以”.py”结尾的文件名,并初始化num_tried字典。
    • update_chat_env方法返回chat_env。
    • break_cycle方法判断是否存在未实现的文件,如果不存在则返回True,否则返回False。
  3. CodeReview类:
    • update_phase_env方法初始化modification_conclusion为空字符串。
    • update_chat_env方法返回chat_env。
    • break_cycle方法判断modification_conclusion是否包含”<INFO> Finished”,如果是则返回True,否则返回False。
  4. Test类:
  • update_phase_env方法初始化一个空字典。
  • update_chat_env方法返回chat_env。
  • break_cycle方法判断exist_bugs_flag是否为False,如果是则打印一条测试通过的信息并返回True,否则返回False。
import importlib
import os
from abc import ABC, abstractmethod
from collections import defaultdict

from camel.typing import ModelType
from chatdev.chat_env import ChatEnv
from chatdev.utils import log_and_print_online


def check_bool(s):
    return s.lower() == "true"


class ComposedPhase(ABC):
    def __init__(self,
                 phase_name: str = None,
                 cycle_num: int = None,
                 composition: list = None,
                 config_phase: dict = None,
                 config_role: dict = None,
                 model_type: ModelType = ModelType.GPT_3_5_TURBO,
                 log_filepath: str = ""
                 ):
        """

        Args:
            phase_name: name of this phase
            cycle_num: loop times of this phase
            composition: list of SimplePhases in this ComposePhase
            config_phase: configuration of all SimplePhases
            config_role: configuration of all Roles
        """

        self.phase_name = phase_name
        self.cycle_num = cycle_num
        self.composition = composition
        self.model_type = model_type
        self.log_filepath = log_filepath

        self.config_phase = config_phase
        self.config_role = config_role

        self.phase_env = dict()

        # init chat turn
        self.chat_turn_limit_default = 10

        # init role
        self.role_prompts = dict()
        for role in self.config_role:
            self.role_prompts[role] = "\n".join(self.config_role[role])

        # init all SimplePhases instances in this ComposedPhase
        self.phases = dict()
        for phase in self.config_phase:
            assistant_role_name = self.config_phase[phase]['assistant_role_name']
            user_role_name = self.config_phase[phase]['user_role_name']
            phase_prompt = "\n".join(self.config_phase[phase]['phase_prompt'])
            phase_module = importlib.import_module("chatdev.phase")
            phase_class = getattr(phase_module, phase)
            phase_instance = phase_class(assistant_role_name=assistant_role_name,
                                         user_role_name=user_role_name,
                                         phase_prompt=phase_prompt,
                                         role_prompts=self.role_prompts,
                                         phase_name=phase,
                                         model_type=self.model_type,
                                         log_filepath=self.log_filepath)
            self.phases[phase] = phase_instance

    @abstractmethod
    def update_phase_env(self, chat_env):
        """
        update self.phase_env (if needed) using chat_env, then the chatting will use self.phase_env to follow the context and fill placeholders in phase prompt
        must be implemented in customized phase
        the usual format is just like:
        ```
            self.phase_env.update({key:chat_env[key]})
        ```
        Args:
            chat_env: global chat chain environment

        Returns: None

        """
        pass

    @abstractmethod
    def update_chat_env(self, chat_env) -> ChatEnv:
        """
        update chan_env based on the results of self.execute, which is self.seminar_conclusion
        must be implemented in customized phase
        the usual format is just like:
        ```
            chat_env.xxx = some_func_for_postprocess(self.seminar_conclusion)
        ```
        Args:
            chat_env:global chat chain environment

        Returns:
            chat_env: updated global chat chain environment

        """
        pass

    @abstractmethod
    def break_cycle(self, phase_env) -> bool:
        """
        special conditions for early break the loop in ComposedPhase
        Args:
            phase_env: phase environment

        Returns: None

        """
        pass

    def execute(self, chat_env) -> ChatEnv:
        """
        similar to Phase.execute, but add control for breaking the loop
        1. receive information from environment(ComposedPhase): update the phase environment from global environment
        2. for each SimplePhase in ComposedPhase
            a) receive information from environment(SimplePhase)
            b) check loop break
            c) execute the chatting
            d) change the environment(SimplePhase)
            e) check loop break
        3. change the environment(ComposedPhase): update the global environment using the conclusion

        Args:
            chat_env: global chat chain environment

        Returns:

        """
        self.update_phase_env(chat_env)
        for cycle_index in range(self.cycle_num):
            for phase_item in self.composition:
                assert phase_item["phaseType"] == "SimplePhase"  # right now we do not support nested composition
                phase = phase_item['phase']
                max_turn_step = phase_item['max_turn_step']
                need_reflect = check_bool(phase_item['need_reflect'])
                log_and_print_online(
                    f"**[Execute Detail]**\n\nexecute SimplePhase:[{phase}] in ComposedPhase:[{self.phase_name}], cycle {cycle_index}")
                if phase in self.phases:
                    self.phases[phase].phase_env = self.phase_env
                    self.phases[phase].update_phase_env(chat_env)
                    if self.break_cycle(self.phases[phase].phase_env):
                        return chat_env
                    chat_env = self.phases[phase].execute(chat_env,
                                                          self.chat_turn_limit_default if max_turn_step <= 0 else max_turn_step,
                                                          need_reflect)
                    if self.break_cycle(self.phases[phase].phase_env):
                        return chat_env
                else:
                    print(f"Phase '{phase}' is not yet implemented. \
                            Please write its config in phaseConfig.json \
                            and implement it in chatdev.phase")
        chat_env = self.update_chat_env(chat_env)
        return chat_env


class Art(ComposedPhase):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)

    def update_phase_env(self, chat_env):
        pass

    def update_chat_env(self, chat_env):
        return chat_env

    def break_cycle(self, chat_env) -> bool:
        return False


class CodeCompleteAll(ComposedPhase):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)

    def update_phase_env(self, chat_env):
        pyfiles = [filename for filename in os.listdir(chat_env.env_dict['directory']) if filename.endswith(".py")]
        num_tried = defaultdict(int)
        num_tried.update({filename: 0 for filename in pyfiles})
        self.phase_env = {
            "max_num_implement": 5,
            "pyfiles": pyfiles,
            "num_tried": num_tried
        }

    def update_chat_env(self, chat_env):
        return chat_env

    def break_cycle(self, phase_env) -> bool:
        if phase_env['unimplemented_file'] == "":
            return True
        else:
            return False


class CodeReview(ComposedPhase):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)

    def update_phase_env(self, chat_env):
        self.phase_env = {"modification_conclusion": ""}

    def update_chat_env(self, chat_env):
        return chat_env

    def break_cycle(self, phase_env) -> bool:
        if "<INFO> Finished".lower() in phase_env['modification_conclusion'].lower():
            return True
        else:
            return False


class Test(ComposedPhase):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)

    def update_phase_env(self, chat_env):
        self.phase_env = dict()

    def update_chat_env(self, chat_env):
        return chat_env

    def break_cycle(self, phase_env) -> bool:
        if not phase_env['exist_bugs_flag']:
            log_and_print_online(f"**[Test Info]**\n\nAI User (Software Test Engineer):\nTest Pass!\n")
            return True
        else:
            return False
作者 east
python 9月 11,2023

清华开源ChatGPT自动编程ChatDev项目chat_env.py代码解读

  • 这段代码定义了两个类,ChatEnvConfig和ChatEnv,用于管理软件开发的环境和资源。
  • ChatEnvConfig类是一个用于存储ChatEnv对象的配置信息的类,它有以下几个属性:
    • self.clear_structure: 一个布尔值,表示是否需要清理结构,即是否需要删除无用的文件。
    • self.brainstorming: 一个布尔值,表示是否需要进行头脑风暴,即是否需要生成一些创意点子。
    • self.gui_design: 一个布尔值,表示是否需要进行GUI设计,即是否需要生成一些图形界面。
    • self.git_management: 一个布尔值,表示是否需要进行Git管理,即是否需要使用Git来管理代码版本。
  • ChatEnvConfig类的__init__()方法是类的构造函数,它接受四个参数clear_structure, brainstorming, gui_design, git_management,并将它们赋值给类的属性。
  • ChatEnvConfig类的__str__()方法是类的字符串表示方法,它返回一个字符串,表示类的属性和值。
  • ChatEnv类是一个用于管理软件开发的环境和资源的类,它有以下几个属性:
    • self.config: 一个ChatEnvConfig对象,表示ChatEnv对象的配置信息。
    • self.roster: 一个Roster对象,表示智能体的名单。
    • self.codes: 一个Codes对象,表示生成的代码的集合。
    • self.proposed_images: 一个字典,表示提出的图像的集合,以图像名称为键,以图像URL为值。
    • self.incorporated_images: 一个字典,表示采纳的图像的集合,以图像名称为键,以图像URL为值。
    • self.requirements: 一个Documents对象,表示需求文档的集合。
    • self.manuals: 一个Documents对象,表示用户手册的集合。
    • self.env_dict: 一个字典,表示软件开发的环境变量,包括以下几个键值对:
      • “directory”: 一个字符串,表示软件保存的目录。
      • “task_prompt”: 一个字符串,表示用户输入的软件想法。
      • “modality”: 一个字符串,表示软件的交互模式。
      • “ideas”: 一个字符串,表示头脑风暴生成的创意点子。
      • “language”: 一个字符串,表示软件使用的编程语言。
      • “review_comments”: 一个字符串,表示代码审查生成的评论。
      • “error_summary”: 一个字符串,表示错误汇总生成的报告。
      • “test_reports”: 一个字符串,表示测试生成的报告。
  • ChatEnv类的__init__()方法是类的构造函数,它接受一个参数chat_env_config,并将其赋值给self.config属性。然后它初始化了其他属性为默认值或空值。
  • ChatEnv类定义了以下几个方法:
    • fix_module_not_found_error()方法是一个静态方法(使用@staticmethod装饰器),用于修复模块未找到错误。它接受一个参数test_reports,表示测试报告。它判断测试报告中是否包含”ModuleNotFoundError”字符串,如果是,则使用re模块来遍历测试报告中匹配”No module named ‘(\S+)’“正则表达式的部分,并将匹配到的模块名称赋值给module变量。然后使用subprocess模块来执行”pip install {}”.format(module)命令,并等待其完成。最后使用log_and_print_online()函数来记录并打印”[CMD Execute]\n\n[CMD] pip install {}”.format(module)信息,表示执行了安装模块的命令。
    • set_directory()方法用于设置软件保存的目录,并更新相关属性。它接受一个参数directory,并断言self.env_dict[‘directory’]属性为空。然后它将directory赋值给self.env_dict[‘directory’]属性、self.codes.directory属性、self.requirements.directory属性和self.manuals.directory属性。接着它判断目录是否存在并且不为空,如果是,则使用time模块来获取当前时间,并将其格式化为”%Y%m%d%H%M%S”形式,并与directory拼接起来,得到new_directory变量。然后使用shutil模块的copytree()函数来将directory复制到new_directory,并打印出”{} Copied to {}“.format(directory, new_directory)信息,表示复制了目录。如果self.config.clear_structure属性为真,则表示需要清理结构,它会判断目录是否存在,如果是,则使用shutil模块的rmtree()函数来删除目录,并使用os模块的mkdir()函数来创建目录,并打印出”{} Created”.format(directory)信息,表示创建了目录。如果不是,则直接使用os模块的mkdir()函数来创建目录。
  • 这段代码定义了一些方法,用于更新、重写、获取和写入软件开发的各种资源,例如代码、图像、文档等。
  • exist_bugs()方法用于检测软件是否存在错误,它不接受任何参数,但返回一个元组(bool, str),其中bool表示是否存在错误,str表示错误信息或成功信息。它首先获取软件保存的目录(self.env_dict[‘directory’]属性),并创建一个字符串success_info,表示软件运行成功的信息。然后它使用try-except语句来捕获可能发生的异常,并执行以下操作:
    • 使用subprocess模块的Popen()函数来执行一个命令,该命令包括切换到软件目录、列出目录内容、运行main.py文件,并将shell参数设为True,preexec_fn参数设为os.setsid,stdout参数和stderr参数设为subprocess.PIPE。这个函数返回一个Popen对象,并将其赋值给process变量。这个对象用于管理子进程的输入和输出。
    • 使用time模块的sleep()函数来等待3秒,让子进程有足够的时间运行。
    • 获取子进程的返回码,并将其赋值给return_code变量。如果返回码为0,则表示子进程运行成功,否则表示子进程运行失败。
    • 使用poll()方法来检查子进程是否仍在运行,如果是,则使用os模块的killpg()函数和signal模块的SIGTERM信号来终止子进程及其所有子进程。
    • 如果返回码为0,则返回(False, success_info)元组,表示软件运行成功。
    • 否则,使用stderr属性和read()方法来读取子进程的错误输出,并将其解码为utf-8格式,并赋值给error_output变量。如果error_output变量不为空,则判断其中是否包含”Traceback”字符串(不区分大小写),如果是,则表示有Python异常发生,它使用replace()方法来去除目录路径,并将其赋值给errs变量。然后返回(True, errs)元组,表示软件运行失败,并附上错误信息。如果error_output变量为空,则返回(False, success_info)元组,表示软件运行成功。
  • recruit()方法用于招聘合适的智能体,并将他们加入到Roster对象中。它接受一个参数agent_name,表示智能体名称。它调用self.roster对象的_recruit()方法,将agent_name作为参数传递,表示招聘该智能体。
  • exist_employee()方法用于判断是否存在某个智能体。它接受一个参数agent_name,表示智能体名称。它调用self.roster对象的_exist_employee()方法,将agent_name作为参数传递,并返回该方法的返回值,表示是否存在该智能体。
  • print_employees()方法用于打印所有智能体的名称。它不接受任何参数,也不返回任何值。它调用self.roster对象的_print_employees()方法,打印所有智能体的名称。
  • update_codes()方法用于更新生成的代码,根据新的LLM回复来比较、修改和保存代码。它接受一个参数generated_content,表示新的LLM回复内容。它调用self.codes对象的_update_codes()方法,将generated_content作为参数传递,并更新代码。
  • rewrite_codes()方法用于重写代码,根据self.codes对象中的内容来修改和保存代码。它不接受任何参数,也不返回任何值。它调用self.codes对象的_rewrite_codes()方法,并将self.config.git_management属性作为参数传递,表示是否进行Git管理,并重写代码。
  • get_codes()方法用于获取代码,根据self.codes对象中的内容来生成一个字符串,表示代码的集合。它不接受任何参数,但返回一个字符串。它调用self.codes对象的_get_codes()方法,并返回该方法的返回值,表示代码的集合。
  • _load_from_hardware()方法用于从硬盘中加载代码,根据给定的目录来读取并保存代码。它接受一个参数directory,表示代码所在的目录。它调用self.codes对象的_load_from_hardware()方法,将directory作为参数传递,并加载代码。
  • _update_requirements()方法用于更新需求文档,根据新的LLM回复来比较、修改和保存文档。它接受一个参数generated_content,表示新的LLM回复内容。它调用self.requirements对象的_update_docs()方法,将generated_content作为参数传递,并更新文档。
  • rewrite_requirements()方法用于重写需求文档,根据self.requirements对象中的内容来修改和保存文档。它不接受任何参数,也不返回任何值。它调用self.requirements对象的_rewrite_docs()方法,并重写文档。
  • get_requirements()方法用于获取需求文档,根据self.requirements对象中的内容来生成一个字符串,表示文档的集合。它不接受任何参数,但返回一个字符串。它调用self.requirements对象的_get_docs()方法,并返回该方法的返回值,表示文档的集合。
  • _update_manuals()方法用于更新用户手册,根据新的LLM回复来比较、修改和保存文档。它接受一个参数generated_content,表示新的LLM回复内容。它调用self.manuals对象的_update_docs()方法,将generated_content、parse=False和predifined_filename=”manual.md”作为参数传递,并更新文档。
  • rewrite_manuals()方法用于重写用户手册,根据self.manuals对象中的内容来修改和保存文档。它不接受任何参数,也不返回任何值。它调用self.manuals对象的_rewrite_docs()方法,并重写文档。
  • write_meta()方法用于写入元数据信息到软件目录中。它不接受任何参数,也不返回任何值。它首先获取软件保存的目录(self.env_dict[‘directory’]属性),并判断目录是否存在并且不为空,如果是,则使用time模块来获取当前时间,并将其格式化为”%Y%m%d%H%M%S”形式,并与目录拼接起来,得到new_directory变量。然后使用shutil模块的copytree()函数来将目录复制到new_directory,并打印出”{} Copied to {}“.format(directory, new_directory)信息,表示复制了目录。如果self.config.clear_structure属性为真,则表示需要清理结构,它会判断目录是否存在,如果是,则使用shutil模块的rmtree()函数来删除目录,并使用os模块的mkdir()函数来创建目录,并打印出”{} Created”.format(directory)信息,表示创建了目录。

generate_images_from_codes 函数的作用是从代码中提取图像文件名,并下载这些图像文件。具体流程如下:

  1. download 函数用于下载图像文件,它接收一个图像的URL和文件名作为参数,使用 requests 库发送 HTTP 请求,并将图像保存到指定的目录。
  2. regex 定义了匹配文件名的正则表达式,它匹配以字母数字字符组成的字符串,以及以 .png 结尾。这个正则表达式将用于从代码中提取图像文件名。
  3. joined_codes 是一个合并了所有代码的字符串。
  4. matches 使用 re.finditer 方法根据正则表达式在 joined_codes 中查找匹配的图像文件名。re.finditer 方法返回一个迭代器,通过遍历这个迭代器可以获得匹配的结果。
  5. 在 for 循环中,遍历所有匹配的文件名,并判断文件名是否存在于 proposed_images 字典中。如果存在,将对应的图像描述添加到 incorporated_images 字典中;否则,直接将文件名添加到 incorporated_images 字典中,并将下划线替换为空格。
  6. 再次使用 for 循环,遍历 incorporated_images 字典中的文件名。如果文件不存在于指定的目录中,则根据图像描述使用 OpenAI 的 API 生成图像,并将生成的图像保存到指定的目录中。

get_proposed_images_from_message 函数的作用是从消息中提取图像文件名和描述,并下载这些图像文件。具体流程如下:

  1. download 函数同样用于下载图像文件。
  2. regex 定义了匹配图像文件名和描述的正则表达式,它匹配以字母数字字符组成的字符串后紧跟冒号和换行符,然后紧跟任意字符。这个正则表达式将用于从消息中提取图像文件名和描述。
  3. matches 使用 re.finditer 方法根据正则表达式在消息中查找匹配的图像文件名和描述。
  4. 在第一个 for 循环中,遍历所有匹配的结果,并将文件名和描述添加到 images 字典中。
  5. 如果 images 字典为空,则执行第二个 for 循环,使用正则表达式提取图像文件名,并根据文件名生成一个默认的描述。
  6. 最后一个 for 循环中,遍历 images 字典中的文件名。如果文件不存在于指定的目录中,则根据图像描述使用 OpenAI 的 API 生成图像,并将生成的图像保存到指定的目录中。

chat_env.py的源代码如下:

import os
import re
import shutil
import signal
import subprocess
import time
from typing import Dict

import openai
import requests

from chatdev.codes import Codes
from chatdev.documents import Documents
from chatdev.roster import Roster
from chatdev.utils import log_and_print_online


class ChatEnvConfig:
    def __init__(self, clear_structure,
                 brainstorming,
                 gui_design,
                 git_management):
        self.clear_structure = clear_structure
        self.brainstorming = brainstorming
        self.gui_design = gui_design
        self.git_management = git_management

    def __str__(self):
        string = ""
        string += "ChatEnvConfig.clear_structure: {}\n".format(self.clear_structure)
        string += "ChatEnvConfig.brainstorming: {}\n".format(self.brainstorming)
        return string


class ChatEnv:
    def __init__(self, chat_env_config: ChatEnvConfig):
        self.config = chat_env_config
        self.roster: Roster = Roster()
        self.codes: Codes = Codes()
        self.proposed_images: Dict[str, str] = {}
        self.incorporated_images: Dict[str, str] = {}
        self.requirements: Documents = Documents()
        self.manuals: Documents = Documents()
        self.env_dict = {
            "directory": "",
            "task_prompt": "",
            "modality": "",
            "ideas": "",
            "language": "",
            "review_comments": "",
            "error_summary": "",
            "test_reports": ""
        }

    @staticmethod
    def fix_module_not_found_error(test_reports):
        if "ModuleNotFoundError" in test_reports:
            for match in re.finditer(r"No module named '(\S+)'", test_reports, re.DOTALL):
                module = match.group(1)
                subprocess.Popen("pip install {}".format(module), shell=True).wait()
                log_and_print_online("**[CMD Execute]**\n\n[CMD] pip install {}".format(module))

    def set_directory(self, directory):
        assert len(self.env_dict['directory']) == 0
        self.env_dict['directory'] = directory
        self.codes.directory = directory
        self.requirements.directory = directory
        self.manuals.directory = directory

        if os.path.exists(self.env_dict['directory']) and len(os.listdir(directory)) > 0:
            new_directory = "{}.{}".format(directory, time.strftime("%Y%m%d%H%M%S", time.localtime()))
            shutil.copytree(directory, new_directory)
            print("{} Copied to {}".format(directory, new_directory))
        if self.config.clear_structure:
            if os.path.exists(self.env_dict['directory']):
                shutil.rmtree(self.env_dict['directory'])
                os.mkdir(self.env_dict['directory'])
                print("{} Created".format(directory))
            else:
                os.mkdir(self.env_dict['directory'])

    def exist_bugs(self) -> tuple[bool, str]:
        directory = self.env_dict['directory']

        success_info = "The software run successfully without errors."
        try:
            command = "cd {}; ls -l; python3 main.py;".format(directory)
            process = subprocess.Popen(command, shell=True, preexec_fn=os.setsid,
                                       stdout=subprocess.PIPE, stderr=subprocess.PIPE)
            time.sleep(3)
            return_code = process.returncode
            # Check if the software is still running
            if process.poll() is None:
                os.killpg(os.getpgid(process.pid), signal.SIGTERM)
            if return_code == 0:
                return False, success_info
            else:
                error_output = process.stderr.read().decode('utf-8')
                if error_output:
                    if "Traceback".lower() in error_output.lower():
                        errs = error_output.replace(directory + "/", "")
                        return True, errs
                else:
                    return False, success_info
        except subprocess.CalledProcessError as e:
            return True, f"Error: {e}"
        except Exception as ex:
            return True, f"An error occurred: {ex}"

        return False, success_info

    def recruit(self, agent_name: str):
        self.roster._recruit(agent_name)

    def exist_employee(self, agent_name: str) -> bool:
        return self.roster._exist_employee(agent_name)

    def print_employees(self):
        self.roster._print_employees()

    def update_codes(self, generated_content):
        self.codes._update_codes(generated_content)

    def rewrite_codes(self) -> None:
        self.codes._rewrite_codes(self.config.git_management)

    def get_codes(self) -> str:
        return self.codes._get_codes()

    def _load_from_hardware(self, directory) -> None:
        self.codes._load_from_hardware(directory)

    def _update_requirements(self, generated_content):
        self.requirements._update_docs(generated_content)

    def rewrite_requirements(self):
        self.requirements._rewrite_docs()

    def get_requirements(self) -> str:
        return self.requirements._get_docs()

    def _update_manuals(self, generated_content):
        self.manuals._update_docs(generated_content, parse=False, predifined_filename="manual.md")

    def rewrite_manuals(self):
        self.manuals._rewrite_docs()

    def write_meta(self) -> None:
        directory = self.env_dict['directory']

        if not os.path.exists(directory):
            os.mkdir(directory)
            print("{} Created.".format(directory))

        meta_filename = "meta.txt"
        with open(os.path.join(directory, meta_filename), "w", encoding="utf-8") as writer:
            writer.write("{}:\n{}\n\n".format("Task", self.env_dict['task_prompt']))
            writer.write("{}:\n{}\n\n".format("Config", self.config.__str__()))
            writer.write("{}:\n{}\n\n".format("Roster", ", ".join(self.roster.agents)))
            writer.write("{}:\n{}\n\n".format("Modality", self.env_dict['modality']))
            writer.write("{}:\n{}\n\n".format("Ideas", self.env_dict['ideas']))
            writer.write("{}:\n{}\n\n".format("Language", self.env_dict['language']))
            writer.write("{}:\n{}\n\n".format("Code_Version", self.codes.version))
            writer.write("{}:\n{}\n\n".format("Proposed_images", len(self.proposed_images.keys())))
            writer.write("{}:\n{}\n\n".format("Incorporated_images", len(self.incorporated_images.keys())))
        print(os.path.join(directory, meta_filename), "Wrote")

    def generate_images_from_codes(self):
        def download(img_url, file_name):
            r = requests.get(img_url)
            filepath = os.path.join(self.env_dict['directory'], file_name)
            if os.path.exists(filepath):
                os.remove(filepath)
            with open(filepath, "wb") as f:
                f.write(r.content)
                print("{} Downloaded".format(filepath))

        regex = r"(\w+.png)"
        joined_codes = self.get_codes()
        matches = re.finditer(regex, joined_codes, re.DOTALL)
        # matched_images = {}
        for match in matches:
            filename = match.group(1).strip()
            if filename in self.proposed_images.keys():
                self.incorporated_images[filename] = self.proposed_images[filename]
            else:
                self.incorporated_images[filename] = filename.replace("_", " ")

        for filename in self.incorporated_images.keys():
            if not os.path.exists(os.path.join(self.env_dict['directory'], filename)):
                desc = self.incorporated_images[filename]
                if desc.endswith(".png"):
                    desc = desc.replace(".png", "")
                print("{}: {}".format(filename, desc))
                response = openai.Image.create(
                    prompt=desc,
                    n=1,
                    size="256x256"
                )
                image_url = response['data'][0]['url']
                download(image_url, filename)

    def get_proposed_images_from_message(self, messages):
        def download(img_url, file_name):
            r = requests.get(img_url)
            filepath = os.path.join(self.env_dict['directory'], file_name)
            if os.path.exists(filepath):
                os.remove(filepath)
            with open(filepath, "wb") as f:
                f.write(r.content)
                print("{} Downloaded".format(filepath))

        regex = r"(\w+.png):(.*?)\n"
        matches = re.finditer(regex, messages, re.DOTALL)
        images = {}
        for match in matches:
            filename = match.group(1).strip()
            desc = match.group(2).strip()
            images[filename] = desc

        if len(images.keys()) == 0:
            regex = r"(\w+.png)"
            matches = re.finditer(regex, messages, re.DOTALL)
            images = {}
            for match in matches:
                filename = match.group(1).strip()
                desc = " ".join(filename.replace(".png", "").split("_"))
                images[filename] = desc
                print("{}: {}".format(filename, images[filename]))

        for filename in images.keys():
            if not os.path.exists(os.path.join(self.env_dict['directory'], filename)):
                desc = images[filename]
                if desc.endswith(".png"):
                    desc = desc.replace(".png", "")
                print("{}: {}".format(filename, desc))
                response = openai.Image.create(
                    prompt=desc,
                    n=1,
                    size="256x256"
                )
                image_url = response['data'][0]['url']
                download(image_url, filename)

        return images
作者 east
chatgpt, python 9月 11,2023

清华开源ChatGPT自动编程ChatDev项目chat_chain.py解读

  • ChatChain类是一个用于实现软件开发的多智能体协作系统,它可以根据用户的自然语言描述来创建定制的软件。
  • __init__()方法是类的构造函数,它接受以下几个参数:
    • config_path: 一个字符串,表示ChatChainConfig.json文件的路径,这个文件包含了ChatChain的基本配置信息,例如智能体链、招聘条件、是否清理结构、是否进行头脑风暴等。
    • config_phase_path: 一个字符串,表示PhaseConfig.json文件的路径,这个文件包含了软件开发的各个阶段的配置信息,例如阶段名称、阶段目标、阶段角色、阶段限制等。
    • config_role_path: 一个字符串,表示RoleConfig.json文件的路径,这个文件包含了软件开发的各个角色的配置信息,例如角色名称、角色描述、角色提示等。
    • task_prompt: 一个字符串,表示用户输入的软件想法,例如“我想要一个五子棋游戏”。
    • project_name: 一个字符串,表示用户输入的软件名称,例如“Gomoku”。
    • org_name: 一个字符串,表示用户所属的组织名称,例如“OpenBMB”。
    • model_type: 一个枚举类型,表示使用的大型语言模型(LLM)的类型,例如ModelType.GPT_3_5_TURBO。
  • __init__()方法首先将这些参数保存在类的属性中,然后使用open()函数和json模块来打开和解析这些配置文件,并将配置信息保存在类的属性中。接着它根据配置信息初始化了ChatChain的智能体链和招聘条件,并设置了默认的最大对话轮数为10。然后它根据配置信息创建了一个ChatEnvConfig对象和一个ChatEnv对象,用于管理软件开发的环境和资源。接下来它将用户输入的软件想法保存在类的属性中,并根据配置信息决定是否对其进行自我改进(这个过程在类的preprocess()方法中实现)。然后它根据配置信息创建了一个字典对象self.role_prompts,用于存储各个角色的提示信息。最后它调用类的get_logfilepath()方法来获取日志文件的路径,并将其保存在类的属性中。
  • check_bool()函数是一个辅助函数,用于将字符串转换为布尔值。它接受一个参数s,表示一个字符串。它将s转换为小写,并判断是否等于”true”。如果是,则返回True,否则返回False。
  • get_logfilepath()方法用于获取日志文件的路径,并返回一个元组(start_time, log_filepath),其中start_time表示开始时间,log_filepath表示日志文件路径。这个方法首先使用datetime模块来获取当前时间,并将其格式化为”%Y-%m-%d %H:%M:%S”形式,并赋值给start_time变量。然后它使用os模块来获取当前工作目录,并将其与”logs”和start_time拼接起来,得到log_filepath变量。最后它返回(start_time, log_filepath)元组。
  • make_recruitment()方法用于招聘合适的智能体,并将他们加入到ChatEnv对象中。它遍历配置信息中定义的招聘条件(self.recruitments属性),对于每个条件(即智能体名称),它调用ChatEnv对象的recruit()方法,将智能体名称作为参数传递,表示招聘该智能体。
  • execute_step()方法用于执行单个软件开发阶段,它接受一个参数phase_item,表示配置信息中定义的单个阶段信息。它首先获取阶段的名称、类型等信息,并根据不同的类型来执行不同的操作。如果阶段类型是”SimplePhase”,则表示这是一个简单的阶段,它会从self.phases属性中获取相应的SimplePhase对象,并调用其execute()方法,将ChatEnv对象、最大对话轮数、是否需要反思等参数传递给该方法,并将返回的新的ChatEnv对象赋值给self.chat_env属性。如果阶段类型是”ComposedPhase”,则表示这是一个复合的阶段,它会从self.compose_phase_module模块中获取相应的ComposedPhase类,并创建一个ComposedPhase对象,将阶段名称、循环次数、组成部分、配置信息、模型类型、日志文件路径等参数传递给其构造函数,并将该对象赋值给compose_phase_instance变量。然后它调用compose_phase_instance对象的execute()方法,将ChatEnv对象作为参数传递,并将返回的新的ChatEnv对象赋值给self.chat_env属性。如果阶段类型是其他类型,则抛出一个异常,表示未实现该类型。
  • execute_chain()方法用于执行整个软件开发过程,它遍历配置信息中定义的智能体链(self.chain属性),对于每个阶段信息,它调用execute_step()方法来执行该阶段。
  • get_logfilepath()方法用于获取日志文件的路径,并返回一个元组(start_time, log_filepath),其中start_time表示开始时间,log_filepath表示日志文件路径。这个方法首先使用datetime模块来获取当前时间,并将其格式化为”%Y-%m-%d %H:%M:%S”形式,并赋值给start_time变量。然后它使用os模块来获取当前工作目录,并将其与”logs”和start_time拼接起来,得到log_filepath变量。最后它返回(start_time, log_filepath)元组。
  • pre_processing()方法用于进行预处理,例如删除无用的文件和记录一些全局的配置信息。它不接受任何参数,也不返回任何值。它首先判断ChatEnv对象的配置信息中是否需要清理结构(self.chat_env.config.clear_structure属性),如果是,则使用os模块来遍历WareHouse目录中的所有文件,并删除除了.py和.log以外的文件,并打印出删除的文件路径。然后它获取软件保存的目录(由项目名称、组织名称和开始时间拼接而成),并调用ChatEnv对象的set_directory()方法,将该目录作为参数传递,表示设置该目录为软件目录。接着它使用shutil模块的copy()函数来将配置文件复制到软件目录中,并使用open()函数和write()方法来将用户输入的软件想法写入到软件目录中的一个.prompt文件中。然后它创建一个字符串preprocess_msg,并赋值为”[Preprocessing]\n\n”,表示开始预处理。接着它创建一个ChatGPTConfig对象,并将其赋值给chat_gpt_config变量,表示LLM的配置信息。然后它在preprocess_msg字符串后面追加一些信息,例如开始时间、配置文件路径、软件想法、项目名称、日志文件路径、ChatDevConfig对象、ChatGPTConfig对象等,并使用log_and_print_online()函数来将preprocess_msg字符串记录到日志文件中,并打印出来。最后它判断配置信息中是否需要进行自我改进(self.config[‘self_improve’]属性),如果是,则调用self.self_task_improve()方法,将用户输入的软件想法作为参数传递,并将返回的更完善的想法赋值给self.chat_env.env_dict[‘task_prompt’]属性。如果不是,则直接将用户输入的软件想法赋值给self.chat_env.env_dict[‘task_prompt’]属性。
  • post_processing()方法用于进行后处理,例如总结产出和移动日志文件到软件目录中。它不接受任何参数,也不返回任何值。它首先调用ChatEnv对象的write_meta()方法,用于写入元数据信息到软件目录中。然后它使用os模块来获取当前工作目录,并将其赋值给filepath变量。接着它使用os模块来获取当前工作目录的父目录,并将其赋值给root变量。然后它创建一个字符串post_info,并赋值为”[Post Info]\n\n”,表示开始后处理。接着它使用datetime模块来获取当前时间,并将其格式化为”%Y%m%d%H%M%S”形式,并赋值给now_time变量。然后它使用datetime模块和strptime()函数来将开始时间和当前时间转换为datetime对象,并分别赋值给datetime1和datetime2变量。接着它使用total_seconds()方法来计算两个datetime对象之间的差异,并将其赋值给duration变量,表示软件开发所花费的时间。然后它在post_info字符串后面追加”Software Info: {}“.format(get_info(self.chat_env.env_dict[‘directory’], self.log_filepath) + “\n\n🕑duration={:.2f}s\n\n”.format(duration)),表示显示软件的信息和开发时间。接着它在post_info字符串后面追加”ChatDev Starts ({})”.format(self.start_time) + “\n\n”,表示显示开始时间。最后它在post_info字符串后面追加”ChatDev Ends ({})”.format(now_time) + “\n\n”,表示显示结束时间。
  • 这段代码定义了一个self_task_improve()方法,用于对用户输入的软件想法进行自我改进,让LLM更好地理解这些想法。它接受一个参数task_prompt,表示用户输入的软件想法。它返回一个字符串revised_task_prompt,表示经过改进的软件想法。
  • 这个方法首先创建一个字符串self_task_improve_prompt,并赋值为一段提示信息,表示要求用户将一个简短的软件设计需求重写为一个详细的提示,让LLM能够根据这个提示来更好地制作这个软件。这个提示信息中包含了用户输入的软件想法(task_prompt参数),以及一些注意事项,例如提示的长度、格式等。然后它创建一个RolePlaying对象role_play_session,并将其赋值给role_play_session变量,表示一个角色扮演的会话。它将以下几个参数传递给RolePlaying类的构造函数:
    • assistant_role_name: 一个字符串,表示助理的角色名称,为”Prompt Engineer”。
    • assistant_role_prompt: 一个字符串,表示助理的角色描述,为”You are an professional prompt engineer that can improve user input prompt to make LLM better understand these prompts.”。
    • user_role_prompt: 一个字符串,表示用户的角色描述,为”You are an user that want to use LLM to build software.”。
    • user_role_name: 一个字符串,表示用户的角色名称,为”User”。
    • task_type: 一个枚举类型,表示任务类型,为TaskType.CHATDEV。
    • task_prompt: 一个字符串,表示任务描述,为”Do prompt engineering on user query”。
    • with_task_specify: 一个布尔值,表示是否需要指定任务类型,为False。
    • model_type: 一个枚举类型,表示使用的LLM的类型,为self.model_type属性。
  • 接着它调用role_play_session对象的init_chat()方法,将None、None和self_task_improve_prompt作为参数传递,并将返回的两个值赋值给_和input_user_msg变量。这个方法用于初始化角色扮演的会话,并返回助理和用户的第一轮对话。其中input_user_msg变量表示用户输入的重写后的软件想法。然后它调用role_play_session对象的step()方法,将input_user_msg和True作为参数传递,并将返回的两个值赋值给assistant_response和user_response变量。这个方法用于进行角色扮演的一步对话,并返回助理和用户的回复。其中assistant_response变量表示助理回复的内容。接着它使用split()方法和strip()方法来从助理回复中提取出改进后的软件想法,并将其赋值给revised_task_prompt变量。然后它调用log_and_print_online()函数来记录并打印助理回复的内容。最后它调用log_and_print_online()函数来记录并打印原始和改进后的软件想法,并返回revised_task_prompt变量。

chat_chain.py的源代码如下:

import importlib
import json
import os
import shutil
from datetime import datetime
import logging
import time

from camel.agents import RolePlaying
from camel.configs import ChatGPTConfig
from camel.typing import TaskType, ModelType
from chatdev.chat_env import ChatEnv, ChatEnvConfig
from chatdev.statistics import get_info
from chatdev.utils import log_and_print_online, now


def check_bool(s):
    return s.lower() == "true"


class ChatChain:

    def __init__(self,
                 config_path: str = None,
                 config_phase_path: str = None,
                 config_role_path: str = None,
                 task_prompt: str = None,
                 project_name: str = None,
                 org_name: str = None,
                 model_type: ModelType = ModelType.GPT_3_5_TURBO) -> None:
        """

        Args:
            config_path: path to the ChatChainConfig.json
            config_phase_path: path to the PhaseConfig.json
            config_role_path: path to the RoleConfig.json
            task_prompt: the user input prompt for software
            project_name: the user input name for software
            org_name: the organization name of the human user
        """

        # load config file
        self.config_path = config_path
        self.config_phase_path = config_phase_path
        self.config_role_path = config_role_path
        self.project_name = project_name
        self.org_name = org_name
        self.model_type = model_type

        with open(self.config_path, 'r', encoding="utf8") as file:
            self.config = json.load(file)
        with open(self.config_phase_path, 'r', encoding="utf8") as file:
            self.config_phase = json.load(file)
        with open(self.config_role_path, 'r', encoding="utf8") as file:
            self.config_role = json.load(file)

        # init chatchain config and recruitments
        self.chain = self.config["chain"]
        self.recruitments = self.config["recruitments"]

        # init default max chat turn
        self.chat_turn_limit_default = 10

        # init ChatEnv
        self.chat_env_config = ChatEnvConfig(clear_structure=check_bool(self.config["clear_structure"]),
                                             brainstorming=check_bool(self.config["brainstorming"]),
                                             gui_design=check_bool(self.config["gui_design"]),
                                             git_management=check_bool(self.config["git_management"]))
        self.chat_env = ChatEnv(self.chat_env_config)

        # the user input prompt will be self-improved (if set "self_improve": "True" in ChatChainConfig.json)
        # the self-improvement is done in self.preprocess
        self.task_prompt_raw = task_prompt
        self.task_prompt = ""

        # init role prompts
        self.role_prompts = dict()
        for role in self.config_role:
            self.role_prompts[role] = "\n".join(self.config_role[role])

        # init log
        self.start_time, self.log_filepath = self.get_logfilepath()

        # init SimplePhase instances
        # import all used phases in PhaseConfig.json from chatdev.phase
        # note that in PhaseConfig.json there only exist SimplePhases
        # ComposedPhases are defined in ChatChainConfig.json and will be imported in self.execute_step
        self.compose_phase_module = importlib.import_module("chatdev.composed_phase")
        self.phase_module = importlib.import_module("chatdev.phase")
        self.phases = dict()
        for phase in self.config_phase:
            assistant_role_name = self.config_phase[phase]['assistant_role_name']
            user_role_name = self.config_phase[phase]['user_role_name']
            phase_prompt = "\n\n".join(self.config_phase[phase]['phase_prompt'])
            phase_class = getattr(self.phase_module, phase)
            phase_instance = phase_class(assistant_role_name=assistant_role_name,
                                         user_role_name=user_role_name,
                                         phase_prompt=phase_prompt,
                                         role_prompts=self.role_prompts,
                                         phase_name=phase,
                                         model_type=self.model_type,
                                         log_filepath=self.log_filepath)
            self.phases[phase] = phase_instance



    def make_recruitment(self):
        """
        recruit all employees
        Returns: None

        """
        for employee in self.recruitments:
            self.chat_env.recruit(agent_name=employee)

    def execute_step(self, phase_item: dict):
        """
        execute single phase in the chain
        Args:
            phase_item: single phase configuration in the ChatChainConfig.json

        Returns:

        """

        phase = phase_item['phase']
        phase_type = phase_item['phaseType']
        # For SimplePhase, just look it up from self.phases and conduct the "Phase.execute" method
        if phase_type == "SimplePhase":
            max_turn_step = phase_item['max_turn_step']
            need_reflect = check_bool(phase_item['need_reflect'])
            if phase in self.phases:
                self.chat_env = self.phases[phase].execute(self.chat_env,
                                                           self.chat_turn_limit_default if max_turn_step <= 0 else max_turn_step,
                                                           need_reflect)
            else:
                raise RuntimeError(f"Phase '{phase}' is not yet implemented in chatdev.phase")
        # For ComposedPhase, we create instance here then conduct the "ComposedPhase.execute" method
        elif phase_type == "ComposedPhase":
            cycle_num = phase_item['cycleNum']
            composition = phase_item['Composition']
            compose_phase_class = getattr(self.compose_phase_module, phase)
            if not compose_phase_class:
                raise RuntimeError(f"Phase '{phase}' is not yet implemented in chatdev.compose_phase")
            compose_phase_instance = compose_phase_class(phase_name=phase,
                                                         cycle_num=cycle_num,
                                                         composition=composition,
                                                         config_phase=self.config_phase,
                                                         config_role=self.config_role,
                                                         model_type=self.model_type,
                                                         log_filepath=self.log_filepath)
            self.chat_env = compose_phase_instance.execute(self.chat_env)
        else:
            raise RuntimeError(f"PhaseType '{phase_type}' is not yet implemented.")

    def execute_chain(self):
        """
        execute the whole chain based on ChatChainConfig.json
        Returns: None

        """
        for phase_item in self.chain:
            self.execute_step(phase_item)

    def get_logfilepath(self):
        """
        get the log path (under the software path)
        Returns:
            start_time: time for starting making the software
            log_filepath: path to the log

        """
        start_time = now()
        filepath = os.path.dirname(__file__)
        # root = "/".join(filepath.split("/")[:-1])
        root = os.path.dirname(filepath)
        # directory = root + "/WareHouse/"
        directory = os.path.join(root, "WareHouse")
        log_filepath = os.path.join(directory, "{}.log".format("_".join([self.project_name, self.org_name,start_time])))
        return start_time, log_filepath

    def pre_processing(self):
        """
        remove useless files and log some global config settings
        Returns: None

        """
        if self.chat_env.config.clear_structure:
            filepath = os.path.dirname(__file__)
            # root = "/".join(filepath.split("/")[:-1])
            root = os.path.dirname(filepath)
            # directory = root + "/WareHouse"
            directory = os.path.join(root, "WareHouse")
            for filename in os.listdir(directory):
                file_path = os.path.join(directory, filename)
                # logs with error trials are left in WareHouse/
                if os.path.isfile(file_path) and not filename.endswith(".py") and not filename.endswith(".log"):
                    os.remove(file_path)
                    print("{} Removed.".format(file_path))

        software_path = os.path.join(directory, "_".join([self.project_name, self.org_name, self.start_time]))
        self.chat_env.set_directory(software_path)

        # copy config files to software path
        shutil.copy(self.config_path, software_path)
        shutil.copy(self.config_phase_path, software_path)
        shutil.copy(self.config_role_path, software_path)

        # write task prompt to software path
        with open(os.path.join(software_path, self.project_name + ".prompt"), "w") as f:
            f.write(self.task_prompt_raw)

        preprocess_msg = "**[Preprocessing]**\n\n"
        chat_gpt_config = ChatGPTConfig()

        preprocess_msg += "**ChatDev Starts** ({})\n\n".format(self.start_time)
        preprocess_msg += "**Timestamp**: {}\n\n".format(self.start_time)
        preprocess_msg += "**config_path**: {}\n\n".format(self.config_path)
        preprocess_msg += "**config_phase_path**: {}\n\n".format(self.config_phase_path)
        preprocess_msg += "**config_role_path**: {}\n\n".format(self.config_role_path)
        preprocess_msg += "**task_prompt**: {}\n\n".format(self.task_prompt_raw)
        preprocess_msg += "**project_name**: {}\n\n".format(self.project_name)
        preprocess_msg += "**Log File**: {}\n\n".format(self.log_filepath)
        preprocess_msg += "**ChatDevConfig**:\n {}\n\n".format(self.chat_env.config.__str__())
        preprocess_msg += "**ChatGPTConfig**:\n {}\n\n".format(chat_gpt_config)
        log_and_print_online(preprocess_msg)

        # init task prompt
        if check_bool(self.config['self_improve']):
            self.chat_env.env_dict['task_prompt'] = self.self_task_improve(self.task_prompt_raw)
        else:
            self.chat_env.env_dict['task_prompt'] = self.task_prompt_raw

    def post_processing(self):
        """
        summarize the production and move log files to the software directory
        Returns: None

        """

        self.chat_env.write_meta()
        filepath = os.path.dirname(__file__)
        # root = "/".join(filepath.split("/")[:-1])
        root = os.path.dirname(filepath)

        post_info = "**[Post Info]**\n\n"
        now_time = now()
        time_format = "%Y%m%d%H%M%S"
        datetime1 = datetime.strptime(self.start_time, time_format)
        datetime2 = datetime.strptime(now_time, time_format)
        duration = (datetime2 - datetime1).total_seconds()

        post_info += "Software Info: {}".format(
            get_info(self.chat_env.env_dict['directory'], self.log_filepath) + "\n\n🕑**duration**={:.2f}s\n\n".format(duration))

        post_info += "ChatDev Starts ({})".format(self.start_time) + "\n\n"
        post_info += "ChatDev Ends ({})".format(now_time) + "\n\n"

        if self.chat_env.config.clear_structure:
            directory = self.chat_env.env_dict['directory']
            for filename in os.listdir(directory):
                file_path = os.path.join(directory, filename)
                if os.path.isdir(file_path) and file_path.endswith("__pycache__"):
                    shutil.rmtree(file_path, ignore_errors=True)
                    post_info += "{} Removed.".format(file_path) + "\n\n"

        log_and_print_online(post_info)

        logging.shutdown()
        time.sleep(1)

        shutil.move(self.log_filepath,
                    os.path.join(root + "/WareHouse", "_".join([self.project_name, self.org_name, self.start_time]),
                                 os.path.basename(self.log_filepath)))

    # @staticmethod
    def self_task_improve(self, task_prompt):
        """
        ask agent to improve the user query prompt
        Args:
            task_prompt: original user query prompt

        Returns:
            revised_task_prompt: revised prompt from the prompt engineer agent

        """
        self_task_improve_prompt = """I will give you a short description of a software design requirement, 
please rewrite it into a detailed prompt that can make large language model know how to make this software better based this prompt,
the prompt should ensure LLMs build a software that can be run correctly, which is the most import part you need to consider.
remember that the revised prompt should not contain more than 200 words, 
here is the short description:\"{}\". 
If the revised prompt is revised_version_of_the_description, 
then you should return a message in a format like \"<INFO> revised_version_of_the_description\", do not return messages in other formats.""".format(
            task_prompt)
        role_play_session = RolePlaying(
            assistant_role_name="Prompt Engineer",
            assistant_role_prompt="You are an professional prompt engineer that can improve user input prompt to make LLM better understand these prompts.",
            user_role_prompt="You are an user that want to use LLM to build software.",
            user_role_name="User",
            task_type=TaskType.CHATDEV,
            task_prompt="Do prompt engineering on user query",
            with_task_specify=False,
            model_type=self.model_type,
        )

        # log_and_print_online("System", role_play_session.assistant_sys_msg)
        # log_and_print_online("System", role_play_session.user_sys_msg)

        _, input_user_msg = role_play_session.init_chat(None, None, self_task_improve_prompt)
        assistant_response, user_response = role_play_session.step(input_user_msg, True)
        revised_task_prompt = assistant_response.msg.content.split("<INFO>")[-1].lower().strip()
        log_and_print_online(role_play_session.assistant_agent.role_name, assistant_response.msg.content)
        log_and_print_online(
            "**[Task Prompt Self Improvement]**\n**Original Task Prompt**: {}\n**Improved Task Prompt**: {}".format(
                task_prompt, revised_task_prompt))
        return revised_task_prompt
作者 east
python 9月 11,2023

清华开源ChatGPT自动编程ChatDev项目codes.py代码解读

  • 这段代码定义了一个Codes类,这个类是用于管理生成的代码的类,它可以根据LLM的回复来提取、格式化、更新和保存代码。
  • Codes类的__init__()方法是类的构造函数,它接受一个参数generated_content,表示LLM的回复内容。它首先初始化了以下几个属性:
    • self.directory: 一个字符串,表示代码保存的目录。
    • self.version: 一个浮点数,表示代码的版本号。
    • self.generated_content: 一个字符串,表示LLM的回复内容。
    • self.codebooks: 一个字典,表示代码的集合,以文件名为键,以代码内容为值。
  • 然后它定义了两个内部函数extract_filename_from_line()和extract_filename_from_code(),用于从LLM的回复中提取文件名。这两个函数都接受一个参数lines或code,表示LLM的回复中的一部分内容。这两个函数都使用re模块来进行正则表达式匹配,并返回匹配到的文件名。如果没有匹配到文件名,则返回空字符串。
  • 接下来,如果generated_content参数不为空,则它使用re模块来遍历LLM的回复中包含在“`符号中的代码段,并将其保存在code变量中。然后它判断code变量是否包含”CODE”字符串,如果是,则跳过这个代码段,因为这是一个占位符。然后它调用extract_filename_from_line()函数来从LLM的回复中提取文件名,并将其保存在filename变量中。如果filename变量为空,则它判断code变量是否包含”__main__“字符串,如果是,则将filename变量赋值为”main.py”,因为这是主程序文件。如果filename变量仍然为空,则它调用extract_filename_from_code()函数来从code变量中提取文件名,并将其保存在filename变量中。最后它断言filename变量不为空,并将其作为键,将经过_format_code()方法格式化后的code变量作为值,添加到self.codebooks字典中。
  • _format_code()方法用于对代码进行格式化,例如去除多余的空行等。它接受一个参数code,表示代码内容。它首先使用splitlines()方法和join()方法来去除空行,并返回格式化后的代码。
  • _update_codes()方法用于更新生成的代码,根据新的LLM的回复来比较、修改和保存代码。它接受一个参数generated_content,表示新的LLM的回复内容。它首先创建一个新的Codes对象new_codes,并将generated_content作为参数传递给其构造函数。然后它导入difflib模块,用于进行文本比较。接着它遍历new_codes对象中的self.codebooks字典,对于每个键值对(即文件名和代码内容),它判断是否存在于self.codebooks字典中,或者是否与self.codebooks字典中相同键对应的值不同。如果是,则表示需要更新代码,并执行以下操作:
    • 创建一个字符串update_codes_content,并赋值为”[Update Codes]\n\n”,表示开始更新代码。
    • 在update_codes_content字符串后面追加”{} updated.\n”.format(key),表示更新了哪个文件。
    • 创建两个字符串old_codes_content和new_codes_content,并分别赋值为self.codebooks字典中相同键对应的值(即旧代码)和new_codes对象中相同键对应的值(即新代码)。如果self.codebooks字典中不存在相同键,则将old_codes_content赋值为”# None”。
    • 使用splitlines()方法将old_codes_content和new_codes_content分割成行列表,并分别赋值给lines_old和lines_new。
    • 使用difflib.unified_diff()函数来生成两个行列表之间的差异,并返回一个生成器对象unified_diff。
    • 使用join()方法将unified_diff生成器对象转换为一个字符串,并赋值给unified_diff。
    • 在update_codes_content字符串后面追加”\n\n” + “””“` ‘’’

‘’’\n”“” + unified_diff + “\n“`”,表示显示代码的差异。 – 调用utils.log_and_print_online()函数来将update_codes_content字符串记录到日志文件中,并打印出来。 – 将new_codes对象中相同键对应的值赋值给self.codebooks字典中相同键对应的值,表示更新代码。

  • _rewrite_codes()方法用于重写代码,根据self.codebooks字典中的内容来修改和保存代码。它接受一个参数git_management,表示是否进行Git管理。它首先获取self.directory属性,表示代码保存的目录,并创建一个字符串rewrite_codes_content,用于记录重写过程。然后它判断目录是否存在并且不为空,如果是,则将self.version属性加一,表示代码的版本号增加。如果目录不存在,则使用os模块的mkdir()函数来创建目录,并在rewrite_codes_content字符串后面追加”{} Created\n”.format(directory),表示创建了目录。
  • 接着它遍历self.codebooks字典中的键值对(即文件名和代码内容),对于每个键值对,它使用os模块的join()函数来拼接目录和文件名,得到文件路径,并将其保存在filepath变量中。然后它使用open()函数和write()方法来打开并写入文件,并在rewrite_codes_content字符串后面追加os.path.join(directory, filename) + ” Wrote\n”,表示写入了文件。
  • 如果git_management参数为真,则表示需要进行Git管理,它会使用os模块的system()函数来执行一些Git命令,例如初始化仓库、添加文件、提交更改等,并将self.version属性作为提交信息。
  • 最后它调用utils.log_and_print_online()函数来将rewrite_codes_content字符串记录到日志文件中,并打印出来。
  • _get_codes()方法用于获取代码,根据self.codebooks字典中的内容来生成一个字符串,表示代码的集合。它首先创建一个空字符串content,然后遍历self.codebooks字典中的键值对(即文件名和代码内容),对于每个键值对,它在content字符串后面追加”{}\n{}\n{}\n\n\n”.format(filename, “python” if filename.endswith(“.py”) else filename.split(“.”)[-1], self.codebooks[filename]),表示显示文件名和代码内容,并根据文件扩展名来指定语言类型。最后它返回content字符串。
  • _load_from_hardware()方法用于从硬盘中加载代码,根据给定的目录来读取并保存代码。它接受一个参数directory,表示代码所在的目录。它首先断言目录中存在以.py结尾的文件,然后使用os模块的walk()函数来遍历目录中的所有文件。对于每个文件,如果文件以.py结尾,则使用open()函数和read()方法来读取文件内容,并将其保存在code变量中。然后将经过_format_code()方法格式化后的code变量作为值,将文件名作为键,添加到self.codebooks字典中。最后调用utils.log_and_print_online()函数来记录并打印”{} files read from {}”.format(len(self.codebooks.keys()), directory),表示从目录中读取了多少个文件。

codes.py的代码如下:

import os
import re

from chatdev.utils import log_and_print_online
import difflib

class Codes:
    def __init__(self, generated_content=""):
        self.directory: str = None
        self.version: float = 1.0
        self.generated_content: str = generated_content
        self.codebooks = {}

        def extract_filename_from_line(lines):
            file_name = ""
            for candidate in re.finditer(r"(\w+\.\w+)", lines, re.DOTALL):
                file_name = candidate.group()
                file_name = file_name.lower()
            return file_name

        def extract_filename_from_code(code):
            file_name = ""
            regex_extract = r"class (\S+?):\n"
            matches_extract = re.finditer(regex_extract, code, re.DOTALL)
            for match_extract in matches_extract:
                file_name = match_extract.group(1)
            file_name = file_name.lower().split("(")[0] + ".py"
            return file_name

        if generated_content != "":
            regex = r"(.+?)\n```.*?\n(.*?)```"
            matches = re.finditer(regex, self.generated_content, re.DOTALL)
            for match in matches:
                code = match.group(2)
                if "CODE" in code:
                    continue
                group1 = match.group(1)
                filename = extract_filename_from_line(group1)
                if "__main__" in code:
                    filename = "main.py"
                if filename == "":  # post-processing
                    filename = extract_filename_from_code(code)
                assert filename != ""
                if filename is not None and code is not None and len(filename) > 0 and len(code) > 0:
                    self.codebooks[filename] = self._format_code(code)

    def _format_code(self, code):
        code = "\n".join([line for line in code.split("\n") if len(line.strip()) > 0])
        return code

    def _update_codes(self, generated_content):
        new_codes = Codes(generated_content)
        differ = difflib.Differ()
        for key in new_codes.codebooks.keys():
            if key not in self.codebooks.keys() or self.codebooks[key] != new_codes.codebooks[key]:
                update_codes_content = "**[Update Codes]**\n\n"
                update_codes_content += "{} updated.\n".format(key)
                old_codes_content = self.codebooks[key] if key in self.codebooks.keys() else "# None"
                new_codes_content = new_codes.codebooks[key]

                lines_old = old_codes_content.splitlines()
                lines_new = new_codes_content.splitlines()

                unified_diff = difflib.unified_diff(lines_old, lines_new, lineterm='', fromfile='Old', tofile='New')
                unified_diff = '\n'.join(unified_diff)
                update_codes_content = update_codes_content + "\n\n" + """```
'''

'''\n""" + unified_diff + "\n```"

                log_and_print_online(update_codes_content)
                self.codebooks[key] = new_codes.codebooks[key]

    def _rewrite_codes(self, git_management) -> None:
        directory = self.directory
        rewrite_codes_content = "**[Rewrite Codes]**\n\n"
        if os.path.exists(directory) and len(os.listdir(directory)) > 0:
            self.version += 1.0
        if not os.path.exists(directory):
            os.mkdir(self.directory)
            rewrite_codes_content += "{} Created\n".format(directory)

        for filename in self.codebooks.keys():
            filepath = os.path.join(directory, filename)
            with open(filepath, "w", encoding="utf-8") as writer:
                writer.write(self.codebooks[filename])
                rewrite_codes_content += os.path.join(directory, filename) + " Wrote\n"

        if git_management:
            if self.version == 1.0:
                os.system("cd {}; git init".format(self.directory))
            os.system("cd {}; git add .".format(self.directory))
            os.system("cd {}; git commit -m \"{}\"".format(self.directory, self.version))

        log_and_print_online(rewrite_codes_content)

    def _get_codes(self) -> str:
        content = ""
        for filename in self.codebooks.keys():
            content += "{}\n```{}\n{}\n```\n\n".format(filename,
                                                       "python" if filename.endswith(".py") else filename.split(".")[
                                                           -1], self.codebooks[filename])
        return content

    def _load_from_hardware(self, directory) -> None:
        assert len([filename for filename in os.listdir(directory) if filename.endswith(".py")]) > 0
        for root, directories, filenames in os.walk(directory):
            for filename in filenames:
                if filename.endswith(".py"):
                    code = open(os.path.join(directory, filename), "r", encoding="utf-8").read()
                    self.codebooks[filename] = self._format_code(code)
        log_and_print_online("{} files read from {}".format(len(self.codebooks.keys()), directory))
作者 east
chatgpt 9月 11,2023

清华开源ChatGPT自动编程ChatDev项目结构和关键代码解析

这个项目的详细解析如下:

  • 项目概述:ChatDev是一个使用自然语言描述的想法来创建定制软件的项目,它通过多智能体协作的方式来实现软件开发的各个阶段,包括设计、编码、测试和文档1。ChatDev的目标是提供一个易于使用、高度可定制和可扩展的框架,基于大型语言模型(LLM),作为研究集体智能的理想场景1。
  • 项目作用:ChatDev可以让用户通过简单的自然语言描述来构建自己想要的软件,无需编程知识或技能。用户只需要提供一个简单的想法,例如“我想要一个五子棋游戏”,就可以让ChatDev的智能体们协同工作,生成一个完整的五子棋软件,包括界面、逻辑、功能和文档1。用户可以在过程中与智能体们进行交互,提供反馈或修改需求,从而得到更满意的结果1。
  • 项目结构:ChatDev的项目结构如下:
    • CompanyConfig:这个文件夹包含了ChatDev的公司配置文件,定义了公司的名称、使命、愿景、价值观、组织结构、角色分配等信息1。用户可以根据自己的喜好修改这些配置文件,从而定制自己的虚拟软件公司1。
    • WareHouse:这个文件夹包含了ChatDev生成的软件仓库,每个仓库对应一个用户提出的想法1。每个仓库中包含了软件的源代码、测试代码、文档等文件,以及一个README.md文件,记录了软件的基本信息和开发过程1。
    • chatdev:这个文件夹包含了ChatDev的核心代码,实现了智能体之间的通信和协作机制,以及各个阶段的任务分配和执行逻辑1。这个文件夹中还包含了一些辅助函数和工具类,用于处理自然语言、生成代码、测试软件等功能1。
    • misc:这个文件夹包含了一些杂项文件,例如五子棋游戏的图形资源1。
    • online_log:这个文件夹包含了ChatDev在线日志模式和回放模式所需的文件1。在线日志模式可以让用户实时查看智能体之间的对话和交互过程,回放模式可以让用户回顾已经完成的软件开发过程1。
    • .gitignore:这个文件用于指定哪些文件或文件夹不需要被Git跟踪1。
    • README.md:这个文件是项目的主要介绍文件,包含了项目的概述、新闻、功能、快速入门、使用方法、常见问题等信息1。
    • requirements.txt:这个文件用于指定项目所需的Python依赖包1。
    • run.py:这个文件是项目的主要运行文件,用于启动ChatDev并接收用户输入1。
    • wiki.md:这个文件是项目的详细文档文件,包含了项目背景、原理、架构、配置、使用示例等信息1。
  • 项目关键代码详细解析:
    • run.py:这个文件是项目运行时最先执行的代码,它首先导入了chatdev模块中定义的Company类,并创建了一个Company对象company。然后它调用company.init()方法来初始化公司配置,并打印出公司名称和使命。接下来它调用company.start()方法来启动公司的运行,这个方法会创建一个新的线程来执行company.run()方法,这个方法是公司运行的主循环。然后它调用company.input()方法来接收用户输入,并将用户输入传递给company.handle_input()方法,这个方法会根据用户输入的内容来执行相应的操作,例如创建新的软件项目、查看已有的软件项目、切换在线日志模式或回放模式等。最后它调用company.stop()方法来停止公司的运行,并释放资源。
    • chatdev/company.py:这个文件定义了Company类,这个类是ChatDev的核心类,代表了一个虚拟的软件公司。Company类有以下几个主要属性和方法:
      • __init__():这个方法是Company类的构造函数,它接受一个参数config,表示公司配置文件的路径。它首先调用load_config()方法来加载配置文件,并将配置信息保存在self.config属性中。然后它创建了一个空列表self.projects,用于存储公司创建的软件项目。接着它创建了一个空字典self.agents,用于存储公司拥有的智能体。最后它创建了一个空队列self.queue,用于存储智能体之间的消息。
      • load_config():这个方法用于加载配置文件,并返回一个字典对象,包含了配置信息。它首先使用json模块打开配置文件,并将其解析为一个Python对象。然后它检查配置信息是否合法,例如是否包含了必要的字段,是否符合预期的格式等。如果配置信息合法,它就返回这个对象,否则它就抛出一个异常。
      • init():这个方法用于初始化公司,根据配置信息创建智能体并分配角色。它首先遍历配置信息中的agents字段,对于每个智能体,它根据其类型和名称创建一个Agent对象,并将其添加到self.agents字典中,以名称为键,对象为值。然后它遍历配置信息中的roles字段,对于每个角色,它根据其名称和成员列表创建一个Role对象,并将其添加到self.agents字典中,以名称为键,对象为值。最后它遍历配置信息中的relations字段,对于每个关系,它根据其类型和成员列表创建一个Relation对象,并将其添加到self.agents字典中,以类型为键,对象为值。
      • start():这个方法用于启动公司的运行,它创建了一个新的线程来执行self.run()方法,并将其保存在self.thread属性中。
      • stop():这个方法用于停止公司的运行,它向self.queue队列中发送一个特殊的消息”STOP”,表示终止信号,并等待self.thread线程结束。
      • run():这个方法是公司运行的主循环,它不断地从self.queue队列中获取消息,并根据消息内容进行处理。如果消息是”STOP”,表示终止信号,它就退出循环并结束线程。如果消息是一个元组(msg, sender, receiver),表示智能体之间的通信消息,它就调用handle_message()方法来处理这个消息。如果消息是其他类型,表示异常情况,它就打印出错误信息并忽略这个消息。
      • handle_message():这个方法用于处理智能体之间的通信消息,它接受三个参数msg, sender, receiver,分别表示消息内容
作者 east
python 9月 8,2023

python自动合成图片为gif,并能根据第一张图片自动统一图片尺寸

网上找来合成图片成gif的代码,没想到运行报错:
Traceback (most recent call last): File “D:\code\python\binance-quantization-master\tools\giftool.py”, line 5, in <module> import imageio.v3 as iio ModuleNotFoundError: No module named ‘imageio.v3’

明明已经运行 pip install imageio 安装模块了。后来分析可能版本旧了,重新升级模块: pip install –upgrade imageio

随便找来几张图片试验:

Traceback (most recent call last): File “D:\code\python\binance-quantization-master\tools\giftool.py”, line 16, in <module> iio.imwrite(‘movie.gif’, images, duration=3, loop=0) File “D:\aiBigData\anaconda3\lib\site-packages\imageio\v3.py”, line 147, in imwrite encoded = img_file.write(image, **kwargs) File “D:\aiBigData\anaconda3\lib\site-packages\imageio\plugins\pillow.py”, line 389, in write ndimage = np.stack(ndimage, axis=0) File “D:\aiBigData\anaconda3\lib\site-packages\numpy\core\shape_base.py”, line 449, in stack raise ValueError(‘all input arrays must have the same shape’) ValueError: all input arrays must have the same shape

导致错误的原因是所有输入的图像数组必须具有相同的形状。这意味着合成 GIF 时,要确保所有的图像具有相同的宽度和高度。 在实际应用场景,也很有可能尺寸大小有轻微不同。

一种简单的方法是使用 PIL 库来调整图像的大小 。

原来的代码:

import imageio.v3 as iio
import os

png_dir = 'images'
images = []

# list file in folder 'images' and sort them by name
image_list = [os.path.join(png_dir, f) for f in os.listdir(png_dir) if f.endswith('.png')]
image_list.sort()

# append images to list
for file_name in image_list:
    images.append(iio.imread(file_name))

# save as gif file
iio.imwrite('movie.gif', images, duration=3, loop=0)

修改后代码:

import imageio.v3 as iio
import os
from PIL import Image

png_dir = 'd:\\tmp'
images = []

# list file in folder 'images' and sort them by name
image_list = [os.path.join(png_dir, f) for f in os.listdir(png_dir) if f.endswith('.png')]
image_list.sort()


# 获取第一张图像的尺寸
first_image = Image.open(os.path.join(png_dir, image_list[0]))
target_size = first_image.size

# 循环处理图像并调整大小
for file_name in image_list:
    image = Image.open(file_name)
    resized_image = image.resize(target_size)
    images.append(resized_image)


# save as gif file
iio.imwrite('movie.gif', images, duration=3, loop=0)
作者 east
Java, python 9月 7,2023

用ChatGPT自动生成流程图

我们看别人代码时,总希望有流程图,这样可以一目了然,不过自己写的代码,又不想花几个小时去画流程图。有没有更好的方法呢?

方法就是用ChatGPT等大模型自动生成流程图,并用python等语言实现自动输出流程图。

1、生成流程图的 Mermaid语法

ChatGPT提示语:

对下面的代码生成流程图,并用Mermaid语法输出 。

2、把Mermaid语法的流程图输出图片

要使用Python或Java生成Mermaid语法输出的流程图图片,您可以使用以下方法:

Python 方法:

  1. 使用 mermaid-cli 工具来将Mermaid代码转换为图片。首先,安装mmdc(mermaid-cli的执行程序)。
  2. 使用Python调用mmdc。
import os

def generate_mermaid_image(mermaid_code, output_path):
    with open("temp.mmd", "w") as file:
        file.write(mermaid_code)
    os.system(f"mmdc -i temp.mmd -o {output_path}")
    os.remove("temp.mmd")

mermaid_code = """
graph TD;
    A-->B;
    A-->C;
    B-->D;
    C-->D;
"""

generate_mermaid_image(mermaid_code, "output.png")

Java 方法:

  1. 与Python方法相同,首先安装 mermaid-cli。
  2. 使用Java的 Runtime 类来调用 mmdc。
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;

public class MermaidGenerator {

    public static void generateMermaidImage(String mermaidCode, String outputPath) throws IOException, InterruptedException {
        File tempFile = File.createTempFile("temp", ".mmd");
        try (FileWriter writer = new FileWriter(tempFile)) {
            writer.write(mermaidCode);
        }

        Process process = Runtime.getRuntime().exec("mmdc -i " + tempFile.getAbsolutePath() + " -o " + outputPath);
        process.waitFor();

        tempFile.delete();
    }

    public static void main(String[] args) throws IOException, InterruptedException {
        String mermaidCode = """
            graph TD;
                A-->B;
                A-->C;
                B-->D;
                C-->D;
            """;
        generateMermaidImage(mermaidCode, "output.png");
    }
}

请注意,这两种方法都需要您在计算机上安装并配置mermaid-cli。此外,这两种方法都是使用临时文件来存储Mermaid代码,并在转换后删除它。这是为了简化调用mmdc的过程,但您可以根据需要进行调整。

作者 east
python 9月 7,2023

python来操作ppt

自动化创建 PowerPoint 演示文稿,仅添加文字而不修改图形,可以使用 Python 和 Python-pptx 库来实现。下面是一个详细的解决方案:

步骤 1:准备 PowerPoint 模板

  • 创建一个 PowerPoint 模板,其中包含您想要的样式、布局和占位符文本框。确保在模板中为每个要添加文字的位置添加文本框(占位符)。

步骤 2:安装 Python-pptx 库

  • 使用 pip 安装 Python-pptx 库,这是一个用于生成 PowerPoint 文件的库。
python复制代码pip install python-pptx

步骤 3:编写 Python 脚本

  • 创建一个 Python 脚本,以自动化生成 PowerPoint 演示文稿。以下是一个示例脚本:
from pptx import Presentation

# 1. 打开 PowerPoint 模板
ppt = Presentation('your_template.pptx')

# 2. 选择要添加文字的幻灯片和文本框(占位符)
slide_index = 0  # 幻灯片索引,从0开始
textbox_index = 0  # 文本框索引,从0开始

slide = ppt.slides[slide_index]
textbox = slide.shapes[textbox_index]

# 3. 添加文字到文本框
text_to_add = "这是要添加的文本。"
textbox.text = text_to_add

# 4. 保存生成的 PowerPoint 文件
ppt.save('generated_presentation.pptx')
  • 在脚本中,您可以指定要添加文字的幻灯片索引和文本框索引。然后,将要添加的文本赋值给文本框的 .text 属性。

步骤 4:运行脚本

  • 运行 Python 脚本,它将打开 PowerPoint 模板、添加指定的文字,然后保存生成的 PowerPoint 文件。

这个解决方案基于现有的 PowerPoint 模板创建演示文稿,仅添加文字而不修改图形或样式。您可以根据需要扩展脚本,以在多个幻灯片和文本框上添加不同的文字内容。请确保您的模板和脚本的格式和布局匹配,以获得所需的结果。

作者 east
mysql, 大数据开发, 提示词 9月 7,2023

java批量生成海量测试数据及用ChatGPT提示语一键生成的方法

在做大数据开发时,为了测试性能等,需要上千万,甚至TB或PB级别的,在测试环境可能没有那么多数据,这时可以考虑进行造测试数据。

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.Random;

public class TestDataGenerator {
    public static void main(String[] args) {
        String url = "jdbc:mysql://localhost:3306/your_database";
        String username = "your_username";
        String password = "your_password";
        int batchSize = 1000; // 每批次插入的数据量
        int totalRecords = 1000000; // 总共要生成的数据量

        try {
            Connection connection = DriverManager.getConnection(url, username, password);
            connection.setAutoCommit(false);

            String insertQuery = "INSERT INTO test (id, callid, type, ...其他列...) VALUES (?, ?, ?, ...其他值...)";
            PreparedStatement preparedStatement = connection.prepareStatement(insertQuery);

            Random random = new Random();

            for (int i = 1; i <= totalRecords; i++) {
                // 设置每个字段的值,根据表结构设置对应的数据生成逻辑
                preparedStatement.setLong(1, i);
                preparedStatement.setString(2, "CallSheet" + i);
                preparedStatement.setString(3, "Type" + (random.nextInt(5) + 1));
                // 设置其他字段的值...

                preparedStatement.addBatch();

                if (i % batchSize == 0) {
                    preparedStatement.executeBatch();
                    connection.commit();
                }
            }

            preparedStatement.executeBatch();
            connection.commit();

            preparedStatement.close();
            connection.close();

            System.out.println("测试数据生成完成!");
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

请将上述示例中的数据库连接信息和插入逻辑根据您的数据库设置和表结构进行相应的修改。此程序将会在数据库中插入海量测试数据。

更方便的方法是在ChatGPT等大模型,输入下面提示语:

根据下面的表结构,生成100万的测试数据,给出详细的java实现代码或存储过程代码:【表结构】

亲测在New Bing是可以生成可以运行的代码。

作者 east

上一 1 2 3 … 19 下一个

关注公众号“大模型全栈程序员”回复“小程序”获取1000个小程序打包源码。回复”chatgpt”获取免注册可用chatgpt。回复“大数据”获取多本大数据电子书

标签

AIGC AI创作 bert chatgpt github GPT-3 gpt3 GTP-3 hive mysql O2O tensorflow UI控件 不含后台 交流 共享经济 出行 图像 地图定位 外卖 多媒体 娱乐 小程序 布局 带后台完整项目 开源项目 搜索 支付 效率 教育 日历 机器学习 深度学习 物流 用户系统 电商 画图 画布(canvas) 社交 签到 联网 读书 资讯 阅读 预订

官方QQ群

小程序开发群:74052405

大数据开发群: 952493060

近期文章

  • 如何在Chrome中设置启动时自动打开多个默认网页
  • spark内存溢出怎样区分是软件还是代码原因
  • MQTT完全解析和实践
  • 解决运行Selenium报错:self.driver = webdriver.Chrome(service=service) TypeError: __init__() got an unexpected keyword argument ‘service’
  • python 3.6使用mysql-connector-python报错:SyntaxError: future feature annotations is not defined
  • 详解Python当中的pip常用命令
  • AUTOSAR如何在多个供应商交付的配置中避免ARXML不兼容?
  • C++thread pool(线程池)设计应关注哪些扩展性问题?
  • 各类MCAL(Microcontroller Abstraction Layer)如何与AUTOSAR工具链解耦?
  • 如何设计AUTOSAR中的“域控制器”以支持未来扩展?

文章归档

  • 2025年7月
  • 2025年6月
  • 2025年5月
  • 2025年4月
  • 2025年3月
  • 2025年2月
  • 2025年1月
  • 2024年12月
  • 2024年11月
  • 2024年10月
  • 2024年9月
  • 2024年8月
  • 2024年7月
  • 2024年6月
  • 2024年5月
  • 2024年4月
  • 2024年3月
  • 2023年11月
  • 2023年10月
  • 2023年9月
  • 2023年8月
  • 2023年7月
  • 2023年6月
  • 2023年5月
  • 2023年4月
  • 2023年3月
  • 2023年1月
  • 2022年11月
  • 2022年10月
  • 2022年9月
  • 2022年8月
  • 2022年7月
  • 2022年6月
  • 2022年5月
  • 2022年4月
  • 2022年3月
  • 2022年2月
  • 2022年1月
  • 2021年12月
  • 2021年11月
  • 2021年9月
  • 2021年8月
  • 2021年7月
  • 2021年6月
  • 2021年5月
  • 2021年4月
  • 2021年3月
  • 2021年2月
  • 2021年1月
  • 2020年12月
  • 2020年11月
  • 2020年10月
  • 2020年9月
  • 2020年8月
  • 2020年7月
  • 2020年6月
  • 2020年5月
  • 2020年4月
  • 2020年3月
  • 2020年2月
  • 2020年1月
  • 2019年7月
  • 2019年6月
  • 2019年5月
  • 2019年4月
  • 2019年3月
  • 2019年2月
  • 2019年1月
  • 2018年12月
  • 2018年7月
  • 2018年6月

分类目录

  • Android (73)
  • bug清单 (79)
  • C++ (34)
  • Fuchsia (15)
  • php (4)
  • python (45)
  • sklearn (1)
  • 云计算 (20)
  • 人工智能 (61)
    • chatgpt (21)
      • 提示词 (6)
    • Keras (1)
    • Tensorflow (3)
    • 大模型 (1)
    • 智能体 (4)
    • 深度学习 (14)
  • 储能 (44)
  • 前端 (5)
  • 大数据开发 (491)
    • CDH (6)
    • datax (4)
    • doris (31)
    • Elasticsearch (15)
    • Flink (78)
    • flume (7)
    • Hadoop (19)
    • Hbase (23)
    • Hive (41)
    • Impala (2)
    • Java (71)
    • Kafka (10)
    • neo4j (5)
    • shardingsphere (6)
    • solr (5)
    • Spark (100)
    • spring (11)
    • 数据仓库 (9)
    • 数据挖掘 (7)
    • 海豚调度器 (10)
    • 运维 (34)
      • Docker (3)
  • 小游戏代码 (1)
  • 小程序代码 (139)
    • O2O (16)
    • UI控件 (5)
    • 互联网类 (23)
    • 企业类 (6)
    • 地图定位 (9)
    • 多媒体 (6)
    • 工具类 (25)
    • 电商类 (22)
    • 社交 (7)
    • 行业软件 (7)
    • 资讯读书 (11)
  • 嵌入式 (71)
    • autosar (63)
    • RTOS (1)
    • 总线 (1)
  • 开发博客 (16)
    • Harmony (9)
  • 技术架构 (6)
  • 数据库 (32)
    • mongodb (1)
    • mysql (13)
    • pgsql (2)
    • redis (1)
    • tdengine (4)
  • 未分类 (7)
  • 程序员网赚 (20)
    • 广告联盟 (3)
    • 私域流量 (5)
    • 自媒体 (5)
  • 量化投资 (4)
  • 面试 (14)

功能

  • 登录
  • 文章RSS
  • 评论RSS
  • WordPress.org

All Rights Reserved by Gitweixin.本站收集网友上传代码, 如有侵犯版权,请发邮件联系yiyuyos@gmail.com删除.