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

月度归档9月 2023

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

  • 首页   /  2023   /  
  • 9月
python 9月 24,2023

selenium自动输入特殊表情符号报错:selenium.common.exceptions.WebDriverException: Message: unknown error: ChromeDriver only supports characters in the BMP

最近在利用selenium调用chatgpt时报错:selenium.common.exceptions.WebDriverException: Message: unknown error: ChromeDriver only supports characters in the BMP。

由于我的应用场景是不需要表情符号的,所以考虑简单粗暴去除表情符号避开这个问题。

一个常用的库是emoji,它提供了一些有用的函数来处理和删除表情符号。

首先,我们需要安装该库。可以使用以下命令来安装emoji库:

pip install emoji

我们可以导入emoji库并使用其中的demojize函数来去除特殊表情符号。

示例如下:

import emoji

text = "I like Python! 😊❤️🐍"
clean_text = emoji.demojize(text)
print(clean_text)
作者 east
python 9月 12,2023

camel-ai的role_playing代码解读

这段代码定义了一个名为RolePlaying的类,用于两个角色之间的角色扮演。

该类接受以下参数:

  • assistant_role_name (str): 助手扮演的角色名称。
  • user_role_name (str): 用户扮演的角色名称。
  • critic_role_name (str): 评论家扮演的角色名称。带有”:obj:"human"“的角色名称会将评论家设置为Human代理,否则将创建一个CriticAgent。
  • task_prompt (str, optional): 需要执行的任务的提示信息。
  • with_task_specify (bool, optional): 是否使用任务指定代理。
  • with_task_planner (bool, optional): 是否使用任务规划器代理。
  • with_critic_in_the_loop (bool, optional): 是否在循环中包括评论家。
  • critic_criteria (str, optional): 评论家代理的评估标准。如果未指定,则设置评估标准以提高任务性能。
  • model_type (ModelType, optional): 用于角色扮演的模型类型。如果指定,将覆盖所有代理中的模型。
  • task_type (TaskType, optional): 执行的任务类型。
  • assistant_agent_kwargs (Dict, optional): 传递给助手代理的附加参数。
  • user_agent_kwargs (Dict, optional): 传递给用户代理的附加参数。
  • task_specify_agent_kwargs (Dict, optional): 传递给任务指定代理的附加参数。
  • task_planner_agent_kwargs (Dict, optional): 传递给任务规划器代理的附加参数。
  • critic_kwargs (Dict, optional): 传递给评论家的附加参数。
  • sys_msg_generator_kwargs (Dict, optional): 传递给系统消息生成器的附加参数。
  • extend_sys_msg_meta_dicts (List[Dict], optional): 用于扩展系统消息元数据字典的字典列表。
  • extend_task_specify_meta_dict (Dict, optional): 用于扩展任务指定元数据字典的字典。
  • output_language (str, optional): 代理输出的语言。

初始化时可以接收很多参数,下面解析一下各个参数的含义和用途:

  • assistant_role_name (str): 机器人角色的名称,通常是assistant
  • user_role_name (str): 用户角色的名称,通常是user
  • critic_role_name: 批评家角色的名称,默认为critic
  • task_prompt (str): 任务描述或者指示
  • with_task_specify (bool): 是否使用TaskSpecifyAgent来生成特定的任务提示
  • with_task_planner (bool): 是否使用TaskPlannerAgent来规划任务
  • with_critic_in_the_loop (bool): 是否让批评家参与对话过程(默认不参与)
  • critic_criteria (Optional[str]): 批评家参与时需要满足的条件,如不为空,则批评家只会在满足条件时参与对话
  • model_type (Optional[ModelType]): 语言模型类型,如GPT-3等,可以为空
  • task_type (TaskType): 任务类型,默认为AI社交任务
  • assistant_agent_kwargs (Optional[Dict]): 机器人agent的配置
  • user_agent_kwargs (Optional[Dict]): 用户agent的配置
  • task_specify_agent_kwargs (Optional[Dict]): 任务指示agent的配置
  • task_planner_agent_kwargs (Optional[Dict]): 任务规划agent的配置
  • critic_kwargs (Optional[Dict]): 批评家agent的配置
  • sys_msg_generator_kwargs (Optional[Dict]): 系统消息生成器的配置
  • extend_sys_msg_meta_dicts (Optional[List[Dict]]): 系统消息的元数据
  • extend_task_specify_meta_dict (Optional[Dict]): 任务指示的元数据
  • output_language (Optional[str]): 输出的语言,可以为空

代码主要功能是初始化Chatbot,其中有一些重要的方法,如init_specified_task_prompt用来生成特定的任务提示,init_planned_task_prompt用来规划任务提示,还有init_critic来初始化批评家。整个类的逻辑比较复杂,需要根据不同的需求进行配置和使用。

init_planned_task_prompt方法用于使用任务规划(agent)来生成一个计划好的任务提示(task prompt),并将其添加到原始任务提示(task prompt)后面。如果没有任务规划(agent),则不会生成计划好的任务提示。

参数说明:

  • task_planner_agent_kwargs:任务规划(agent)的额外参数,在初始化TaskPlannerAgent时传递。
  • output_language:输出语言,agents输出语言的设置。

方法实现:

  1. 根据self.with_task_planner属性判断是否启用了任务规划。
  2. 如果启用了任务规划(agent)且指定了语言模型类型(model_type),则将model_type添加到task_planner_agent_kwargs中。
  3. 使用TaskPlannerAgent初始化一个任务规划(agent)。
  4. 通过调用task_planner_agent.run方法传入任务提示(task prompt)获取计划好的任务提示,并将其附加到任务提示中。
  5. 如果没有启用任务规划(agent),则将self.planned_task_prompt置为None。

get_sys_message_info方法用于获取初始的机器人和用户系统消息,并返回包含系统消息和元数据的元组。

参数说明:

  • assistant_role_name:机器人角色的名称。
  • user_role_name:用户角色的名称。
  • sys_msg_generator:系统消息生成器。
  • extend_sys_msg_meta_dicts:扩展的系统消息元数据字典的列表。

方法实现:

  1. 创建一个由两个字典组成的列表,每个字典都包含任务提示(task prompt)作为键。
  2. 如果extend_sys_msg_meta_dicts为空并且任务类型是AI社交任务或MISALIGNMENT任务,则创建一个包含角色名称的字典列表。
  3. 如果extend_sys_msg_meta_dicts不为空,则将原始的系统消息元数据字典和扩展的系统消息元数据字典进行合并。
  4. 使用sys_msg_generator.from_dicts方法将元数据字典转换为机器人和用户的初始系统消息。
  5. 返回机器人和用户的初始系统消息以及系统消息元数据字典的列表。

init_agents方法用于初始化助手(agent)和用户(agent),并设置它们的初始系统消息。

参数说明:

  • init_assistant_sys_msg:助手(agent)的初始系统消息。
  • assistant_agent_kwargs:传递给助手(agent)的额外参数。
  • init_user_sys_msg:用户(agent)的初始系统消息。
  • user_agent_kwargs:传递给用户(agent)的额外参数。
  • output_language:agents输出语言的设置。

方法实现:

  1. 如果指定了语言模型类型(model_type),则将其添加到助手(agent)和用户(agent)的额外参数中。
  2. 使用ChatAgent初始化助手(agent),并将助手的系统消息保存在self.assistant_sys_msg中。
  3. 使用ChatAgent初始化用户(agent),并将用户的系统消息保存在self.user_sys_msg中。

init_critic方法用于初始化评论者(agent)。如果评论者角色名称是”human”,则创建一个人类评论者,否则根据指定的评论者准则创建一个评论者(agent)。如果没有指定评论者准则,则默认为改善任务性能。

参数说明:

  • critic_role_name:评论者所扮演角色的名称。
  • critic_criteria:评论者(agent)的评价准则。如果没有指定,则将评价准则设置为改善任务性能。
  • critic_kwargs:传递给评论者(agent)的额外参数。
  • sys_msg_generator:用于生成系统消息的生成器。
  • sys_msg_meta_dicts:系统消息元数据字典的列表。

方法实现:

  1. 如果启用了评论者(agent)循环且评论者角色名称是”human”,则创建一个人类评论者。
  2. 否则,根据指定的评论者准则和角色名称创建一个CriticAgent评论者(agent)。如果没有指定评论者准则,则默认为改善任务性能。
  3. 如果指定了语言模型类型(model_type),则将其添加到评论者(agent)的额外参数中。
  4. 使用评论者准则、角色名称和系统消息元数据字典创建评论者的系统消息。
  5. 将评论者的系统消息保存在self.critic_sys_msg中。
  6. 使用CriticAgent初始化评论者(agent)。

init_chat方法用于初始化对话,通过重置助手(agent)和用户(agent),并向它们发送系统消息来重新开始对话。返回助手的介绍性消息和用户的回复消息列表。

方法实现:

  1. 重置助手(agent)和用户(agent)。
  2. 使用chat消息将系统消息再次发送给助手(agent)和用户(agent)。
  3. 创建助手的介绍性消息,其中包含用户系统消息的内容和一些指令。
  4. 创建用户的回复消息,其内容为助手系统消息的内容。
  5. 使用用户的回复消息调用助手(agent)的step方法,返回助手的响应消息。
  6. 如果助手(agent)的响应消息已结束或没有消息,则抛出ValueError异常。
  7. 返回助手的介绍性消息和助手的响应消息列表。

reduce_message_options方法用于处理一系列对话消息,并返回处理后的消息。如果提供了多个消息且with_critic_in_the_loop为False,则抛出ValueError异常。如果未提供任何消息,则抛出ValueError异常。

方法实现:

  1. 如果消息数量为0,则抛出ValueError异常。
  2. 如果消息数量大于1且with_critic_in_the_loop为False,则抛出ValueError异常。
  3. 否则,如果启用了评论者(agent)循环且评论者存在,则调用评论者的reduce_step方法,返回评论者的响应消息,并将其作为处理后的消息。
  4. 否则,将第一个消息作为处理后的消息返回。

RolePlaying类中的step方法。该方法用于推进对话,接收助手发来的消息,使用用户(agent)处理该消息,然后使用助手(agent)处理生成的回复消息。返回一个元组,其中包含助手生成的消息、助手是否终止了对话以及其他助手相关的信息;还有一个元组,其中包含用户生成的消息、用户是否终止了对话以及其他用户相关的信息。

方法实现:

  1. 使用助手(agent)发送的消息调用用户(agent)的step方法,返回用户(agent)的响应。
  2. 如果用户(agent)的响应已经终止或没有消息,则创建一个空的ChatAgentResponse结构体表示助手的消息,并将用户(agent)的终止信息和额外信息作为ChatAgentResponse结构体的一部分返回。
  3. 否则,根据用户(agent)的响应消息使用reduce_message_options方法处理获取用户的消息。
  4. 将处理后的用户消息提交给用户(agent)。
  5. 使用用户消息调用助手(agent)的step方法,返回助手(agent)的响应。
  6. 如果助手(agent)的响应已终止或没有消息,则创建一个空的ChatAgentResponse结构体表示用户的消息,并将助手(agent)的终止信息和额外信息作为ChatAgentResponse结构体的一部分返回。
  7. 否则,根据助手(agent)的响应消息使用reduce_message_options方法处理获取助手的消息。
  8. 将处理后的助手消息提交给助手(agent)。
  9. 返回一个元组,第一个元素为助手的响应消息、助手是否终止了对话以及其他助手相关的信息,第二个元素为用户的响应消息、用户是否终止了对话以及其他用户相关的信息。

源码如下:

# =========== Copyright 2023 @ CAMEL-AI.org. All Rights Reserved. ===========
# Licensed under the Apache License, Version 2.0 (the “License”);
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an “AS IS” BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# =========== Copyright 2023 @ CAMEL-AI.org. All Rights Reserved. ===========
from typing import Dict, List, Optional, Sequence, Tuple, Union

from camel.agents import (
    ChatAgent,
    CriticAgent,
    TaskPlannerAgent,
    TaskSpecifyAgent,
)
from camel.agents.chat_agent import ChatAgentResponse
from camel.generators import SystemMessageGenerator
from camel.human import Human
from camel.messages import BaseMessage
from camel.prompts import TextPrompt
from camel.typing import ModelType, RoleType, TaskType


class RolePlaying:
    r"""Role playing between two agents.

    Args:
        assistant_role_name (str): The name of the role played by the
            assistant.
        user_role_name (str): The name of the role played by the user.
        critic_role_name (str): The name of the role played by the critic.
            Role name with :obj:`"human"` will set critic as a :obj:`Human`
            agent, else will create a :obj:`CriticAgent`.
            (default: :obj:`"critic"`)
        task_prompt (str, optional): A prompt for the task to be performed.
            (default: :obj:`""`)
        with_task_specify (bool, optional): Whether to use a task specify
            agent. (default: :obj:`True`)
        with_task_planner (bool, optional): Whether to use a task planner
            agent. (default: :obj:`False`)
        with_critic_in_the_loop (bool, optional): Whether to include a critic
            in the loop. (default: :obj:`False`)
        critic_criteria (str, optional): Critic criteria for the critic agent.
            If not specified, set the criteria to improve task performance.
        model_type (ModelType, optional): Model type that will be used for
            role playing. If specified, it will override the model in all
            agents. (default: :obj:`None`)
        task_type (TaskType, optional): The type of task to perform.
            (default: :obj:`TaskType.AI_SOCIETY`)
        assistant_agent_kwargs (Dict, optional): Additional arguments to pass
            to the assistant agent. (default: :obj:`None`)
        user_agent_kwargs (Dict, optional): Additional arguments to pass to
            the user agent. (default: :obj:`None`)
        task_specify_agent_kwargs (Dict, optional): Additional arguments to
            pass to the task specify agent. (default: :obj:`None`)
        task_planner_agent_kwargs (Dict, optional): Additional arguments to
            pass to the task planner agent. (default: :obj:`None`)
        critic_kwargs (Dict, optional): Additional arguments to pass to the
            critic. (default: :obj:`None`)
        sys_msg_generator_kwargs (Dict, optional): Additional arguments to
            pass to the system message generator. (default: :obj:`None`)
        extend_sys_msg_meta_dicts (List[Dict], optional): A list of dicts to
            extend the system message meta dicts with. (default: :obj:`None`)
        extend_task_specify_meta_dict (Dict, optional): A dict to extend the
            task specify meta dict with. (default: :obj:`None`)
        output_language (str, optional): The language to be output by the
            agents. (default: :obj:`None`)
    """

    def __init__(
        self,
        assistant_role_name: str,
        user_role_name: str,
        *,
        critic_role_name: str = "critic",
        task_prompt: str = "",
        with_task_specify: bool = True,
        with_task_planner: bool = False,
        with_critic_in_the_loop: bool = False,
        critic_criteria: Optional[str] = None,
        model_type: Optional[ModelType] = None,
        task_type: TaskType = TaskType.AI_SOCIETY,
        assistant_agent_kwargs: Optional[Dict] = None,
        user_agent_kwargs: Optional[Dict] = None,
        task_specify_agent_kwargs: Optional[Dict] = None,
        task_planner_agent_kwargs: Optional[Dict] = None,
        critic_kwargs: Optional[Dict] = None,
        sys_msg_generator_kwargs: Optional[Dict] = None,
        extend_sys_msg_meta_dicts: Optional[List[Dict]] = None,
        extend_task_specify_meta_dict: Optional[Dict] = None,
        output_language: Optional[str] = None,
    ) -> None:
        self.with_task_specify = with_task_specify
        self.with_task_planner = with_task_planner
        self.with_critic_in_the_loop = with_critic_in_the_loop
        self.model_type = model_type
        self.task_type = task_type
        self.task_prompt = task_prompt

        self.specified_task_prompt: Optional[TextPrompt] = None
        self.init_specified_task_prompt(assistant_role_name, user_role_name,
                                        task_specify_agent_kwargs,
                                        extend_task_specify_meta_dict,
                                        output_language)

        self.planned_task_prompt: Optional[TextPrompt] = None
        self.init_planned_task_prompt(task_planner_agent_kwargs,
                                      output_language)

        sys_msg_generator = SystemMessageGenerator(
            task_type=self.task_type, **(sys_msg_generator_kwargs or {}))

        (init_assistant_sys_msg, init_user_sys_msg,
         sys_msg_meta_dicts) = self.get_sys_message_info(
             assistant_role_name, user_role_name, sys_msg_generator,
             extend_sys_msg_meta_dicts)

        self.assistant_agent: ChatAgent
        self.user_agent: ChatAgent
        self.assistant_sys_msg: BaseMessage
        self.user_sys_msg: BaseMessage
        self.init_agents(
            init_assistant_sys_msg,
            assistant_agent_kwargs,
            init_user_sys_msg,
            user_agent_kwargs,
            output_language,
        )
        self.critic: Optional[Union[CriticAgent, Human]] = None
        self.critic_sys_msg: Optional[BaseMessage] = None
        self.init_critic(critic_role_name, critic_criteria, critic_kwargs,
                         sys_msg_generator, sys_msg_meta_dicts)

    def init_specified_task_prompt(
            self, assistant_role_name: str, user_role_name: str,
            task_specify_agent_kwargs: Optional[Dict],
            extend_task_specify_meta_dict: Optional[Dict],
            output_language: Optional[str]):
        r"""Use a task specify agent to generate a specified task prompt.
        Generated specified task prompt will be used to replace original
        task prompt. If there is no task specify agent, specified task
        prompt will not be generated.

        Args:
            assistant_role_name (str): The name of the role played by the
                assistant.
            user_role_name (str): The name of the role played by the user.
            task_specify_agent_kwargs (Dict, optional): Additional arguments
                to pass to the task specify agent.
            extend_task_specify_meta_dict (Dict, optional): A dict to extend
                the task specify meta dict with.
            output_language (str, optional): The language to be output by the
                agents.
        """
        if self.with_task_specify:
            task_specify_meta_dict = dict()
            if self.task_type in [TaskType.AI_SOCIETY, TaskType.MISALIGNMENT]:
                task_specify_meta_dict.update(
                    dict(assistant_role=assistant_role_name,
                         user_role=user_role_name))
            task_specify_meta_dict.update(extend_task_specify_meta_dict or {})
            if self.model_type is not None:
                if task_specify_agent_kwargs is None:
                    task_specify_agent_kwargs = {}
                task_specify_agent_kwargs.update(dict(model=self.model_type))
            task_specify_agent = TaskSpecifyAgent(
                task_type=self.task_type,
                output_language=output_language,
                **(task_specify_agent_kwargs or {}),
            )
            self.specified_task_prompt = task_specify_agent.run(
                self.task_prompt,
                meta_dict=task_specify_meta_dict,
            )
            self.task_prompt = self.specified_task_prompt

    def init_planned_task_prompt(self,
                                 task_planner_agent_kwargs: Optional[Dict],
                                 output_language: Optional[str]):
        r"""Use a task plan agent to append a planned task prompt to task
        prompt. The planned task prompt is generated based on the task
        prompt, which can be original task prompt or specified task prompt
        if available. If there is no task plan agent, planned task prompt
        will not be generated.

        Args:
            task_planner_agent_kwargs (Dict, optional): Additional arguments
                to pass to the task planner agent.
            output_language (str, optional): The language to be output by the
                agents.
        """
        if self.with_task_planner:
            if self.model_type is not None:
                if task_planner_agent_kwargs is None:
                    task_planner_agent_kwargs = {}
                task_planner_agent_kwargs.update(dict(model=self.model_type))
            task_planner_agent = TaskPlannerAgent(
                output_language=output_language,
                **(task_planner_agent_kwargs or {}),
            )
            self.planned_task_prompt = task_planner_agent.run(self.task_prompt)
            self.task_prompt = (f"{self.task_prompt}\n"
                                f"{self.planned_task_prompt}")
        else:
            self.planned_task_prompt = None

    def get_sys_message_info(
        self,
        assistant_role_name: str,
        user_role_name: str,
        sys_msg_generator: SystemMessageGenerator,
        extend_sys_msg_meta_dicts: Optional[List[Dict]] = None,
    ) -> Tuple[BaseMessage, BaseMessage, List[Dict]]:
        r"""Get initial assistant and user system message with a list of
        system message meta dicts.

        Args:
            assistant_role_name (str): The name of the role played by the
                assistant.
            user_role_name (str): The name of the role played by the user.
            sys_msg_generator (SystemMessageGenerator): A system message
                generator for agents.
            extend_sys_msg_meta_dicts (List[Dict], optional): A list of dicts
                to extend the system message meta dicts with.

        Returns:
            A tuple containing a `BaseMessage` representing the assistant's
            initial system message, a `BaseMessage` representing the user's
            initial system message, and a list of system message meta dicts.
        """
        sys_msg_meta_dicts = [dict(task=self.task_prompt) for _ in range(2)]
        if (extend_sys_msg_meta_dicts is None and self.task_type in [
                TaskType.AI_SOCIETY,
                TaskType.MISALIGNMENT,
        ]):
            extend_sys_msg_meta_dicts = [
                dict(assistant_role=assistant_role_name,
                     user_role=user_role_name) for _ in range(2)
            ]

        if extend_sys_msg_meta_dicts is not None:
            sys_msg_meta_dicts = [{
                **sys_msg_meta_dict,
                **extend_sys_msg_meta_dict
            } for sys_msg_meta_dict, extend_sys_msg_meta_dict in zip(
                sys_msg_meta_dicts, extend_sys_msg_meta_dicts)]

        init_assistant_sys_msg, init_user_sys_msg = (
            sys_msg_generator.from_dicts(
                meta_dicts=sys_msg_meta_dicts,
                role_tuples=[
                    (assistant_role_name, RoleType.ASSISTANT),
                    (user_role_name, RoleType.USER),
                ],
            ))
        return init_assistant_sys_msg, init_user_sys_msg, sys_msg_meta_dicts

    def init_agents(
        self,
        init_assistant_sys_msg: BaseMessage,
        assistant_agent_kwargs: Optional[Dict],
        init_user_sys_msg: BaseMessage,
        user_agent_kwargs: Optional[Dict],
        output_language: Optional[str],
    ):
        r"""Initialize assistant and user agents with their system messages.

        Args:
            init_assistant_sys_msg (BaseMessage): Assistant agent's initial
                system message.
            assistant_agent_kwargs (Dict, optional): Additional arguments to
                pass to the assistant agent.
            init_user_sys_msg (BaseMessage): User agent's initial system
                message.
            user_agent_kwargs (Dict, optional): Additional arguments to
                pass to the user agent.
            output_language (str, optional): The language to be output by the
                agents.
        """
        if self.model_type is not None:
            if assistant_agent_kwargs is None:
                assistant_agent_kwargs = {}
            assistant_agent_kwargs.update(dict(model=self.model_type))
            if user_agent_kwargs is None:
                user_agent_kwargs = {}
            user_agent_kwargs.update(dict(model=self.model_type))

        self.assistant_agent = ChatAgent(
            init_assistant_sys_msg,
            output_language=output_language,
            **(assistant_agent_kwargs or {}),
        )
        self.assistant_sys_msg = self.assistant_agent.system_message

        self.user_agent = ChatAgent(
            init_user_sys_msg,
            output_language=output_language,
            **(user_agent_kwargs or {}),
        )
        self.user_sys_msg = self.user_agent.system_message

    def init_critic(self, critic_role_name: str,
                    critic_criteria: Optional[str],
                    critic_kwargs: Optional[Dict],
                    sys_msg_generator: SystemMessageGenerator,
                    sys_msg_meta_dicts: List[Dict]):
        r"""Initialize critic agent. If critic role name is :obj:`"human"`,
        create a :obj:`Human` critic agent. Else, create a :obj:`CriticAgent`
        critic agent with specified critic criteria. If the critic criteria
        is not specified, set it to improve task performance.

        Args:
            critic_role_name (str): The name of the role played by the critic.
            critic_criteria (str, optional): Critic criteria for the
                critic agent. If not specified, set the criteria to
                improve task performance.
            critic_kwargs (Dict, optional): Additional arguments to
                pass to the critic.
            sys_msg_generator (SystemMessageGenerator): A system message
                generator for agents.
            sys_msg_meta_dicts (list): A list of system message meta dicts.
        """
        if self.with_critic_in_the_loop:
            if critic_role_name.lower() == "human":
                self.critic = Human(**(critic_kwargs or {}))
            else:
                critic_criteria = (critic_criteria
                                   or "improving the task performance")
                critic_msg_meta_dict = dict(critic_role=critic_role_name,
                                            criteria=critic_criteria,
                                            **sys_msg_meta_dicts[0])
                self.critic_sys_msg = sys_msg_generator.from_dict(
                    critic_msg_meta_dict,
                    role_tuple=(critic_role_name, RoleType.CRITIC),
                )
                if self.model_type is not None:
                    if critic_kwargs is None:
                        critic_kwargs = {}
                    critic_kwargs.update(dict(model=self.model_type))
                self.critic = CriticAgent(
                    self.critic_sys_msg,
                    **(critic_kwargs or {}),
                )

    def init_chat(self) -> Tuple[BaseMessage, List[BaseMessage]]:
        r"""Initializes the chat by resetting both of the assistant and user
        agents, and sending the system messages again to the agents using
        chat messages. Returns the assistant's introductory message and the
        user's response messages.

        Returns:
            A tuple containing a `BaseMessage` representing the assistant's
            introductory message, and a list of `BaseMessage` representing
            the user's response messages.
        """
        self.assistant_agent.reset()
        self.user_agent.reset()

        # Send the system messages again to the agents using chat messages
        assistant_msg = BaseMessage.make_assistant_message(
            role_name=self.assistant_sys_msg.role_name,
            content=(f"{self.user_sys_msg.content}. "
                     "Now start to give me instructions one by one. "
                     "Only reply with Instruction and Input."))

        user_msg = BaseMessage.make_user_message(
            role_name=self.user_sys_msg.role_name,
            content=f"{self.assistant_sys_msg.content}")
        assistant_response = self.assistant_agent.step(user_msg)
        if assistant_response.terminated or assistant_response.msgs is None:
            raise ValueError(f"Assistant agent terminated unexpectedly. "
                             f"Error info: {assistant_response.info}")

        return assistant_msg, assistant_response.msgs

    def reduce_message_options(
        self,
        messages: Sequence[BaseMessage],
    ) -> BaseMessage:
        r"""Processes a sequence of chat messages, returning the processed
        message. If multiple messages are provided and
        `with_critic_in_the_loop` is `False`, raises a `ValueError`.
        If no messages are provided, a `ValueError` will be raised.

        Args:
            messages: A sequence of `BaseMessage` objects to process.

        Returns:
            A single `BaseMessage` representing the processed message.
        """
        if len(messages) == 0:
            raise ValueError("No messages to process.")
        if len(messages) > 1 and not self.with_critic_in_the_loop:
            raise ValueError("Got than one message to process. "
                             f"Num of messages: {len(messages)}.")
        elif self.with_critic_in_the_loop and self.critic is not None:
            critic_response = self.critic.reduce_step(messages)
            processed_msg = critic_response.msg
        else:
            processed_msg = messages[0]

        return processed_msg

    def step(
        self,
        assistant_msg: BaseMessage,
    ) -> Tuple[ChatAgentResponse, ChatAgentResponse]:
        r"""Advances the conversation by taking a message from the assistant,
        processing it using the user agent, and then processing the resulting
        message using the assistant agent. Returns a tuple containing the
        resulting assistant message, whether the assistant agent terminated
        the conversation, and any additional assistant information, as well as
        a tuple containing the resulting user message, whether the user agent
        terminated the conversation, and any additional user information.

        Args:
            assistant_msg: A `BaseMessage` representing the message from the
                assistant.

        Returns:
            A tuple containing two ChatAgentResponse: the first struct contains
            the resulting assistant message, whether the assistant agent
            terminated the conversation, and any additional assistant
            information; the second struct contains the resulting user message,
            whether the user agent terminated the conversation, and any
            additional user information.
        """
        user_response = self.user_agent.step(assistant_msg)
        if user_response.terminated or user_response.msgs is None:
            return (ChatAgentResponse([], False, {}),
                    ChatAgentResponse([], user_response.terminated,
                                      user_response.info))
        user_msg = self.reduce_message_options(user_response.msgs)
        self.user_agent.submit_message(user_msg)

        assistant_response = self.assistant_agent.step(user_msg)
        if assistant_response.terminated or assistant_response.msgs is None:
            return (ChatAgentResponse([], assistant_response.terminated,
                                      assistant_response.info),
                    ChatAgentResponse([user_msg], False, user_response.info))
        assistant_msg = self.reduce_message_options(assistant_response.msgs)
        self.assistant_agent.submit_message(assistant_msg)

        return (
            ChatAgentResponse([assistant_msg], assistant_response.terminated,
                              assistant_response.info),
            ChatAgentResponse([user_msg], user_response.terminated,
                              user_response.info),
        )
作者 east
python 9月 12,2023

亲测可用!!!Centos7安装chrome+chromedriver以便实现selenium自动化详细教程

网上很多教程都是在线安装chrome,这样安装了最新稳定的chrome,可惜我遇到chromdriver的版本跟上 chrome,为了早日实现在centos服务selenium自动化,不可能去等待 chromdriver 更新,只能 chrome进行降版本来离线安装。花了几个小时找资源,终于找到一个可用的:

安装chrome

一、下载安装包链接: https://pan.baidu.com/s/1ScyMB54eoFfGXq7-RapsdA 提取码: amnp

二、安装流程

rpm -ivh vulkan-filesystem-1.1.97.0-1.el7.norch.rpm

rpm -ivh vulkan-1.1.97.0-1.el7.x86_64.rpm

rpm -ivh liberation-narrow-fonts-1.07.2-16.el7.noarch.rpm

rpm -ivh liberation-fonts-1.07.2-16.el7.noarch.rpm

rpm -i google-chrome-stable-current_x86_x64.rpm

chromedriver安装

chromedriver下载
在https://npm.taobao.org/mirrors/chromedriver/中下载对应版本的chromedriver,上面的chrome对应下面这个版本的:

https://cdn.npmmirror.com/binaries/chromedriver/103.0.5060.134/chromedriver_linux64.zip

解压软件:可在windows下下载, 解压后再转移过去,或unzip chromedriver_linux64.zip
将软件移至对应目录下(很重要)
mv chromedriver /usr/bin/

赋权限
chmod +x /usr/bin/chromedriver

验证安装完成
直接输入chromedriver

编写python测试chrome以及驱动是否能够正常运行

测试代码test_google.py

from selenium import webdriver
from selenium.webdriver.chrome.options import Options

# 创建 ChromeOptions 实例,启用无头模式
chrome_options = Options()
chrome_options.add_argument('--headless')
chrome_options.add_argument('--no-sandbox')
chrome_options.add_argument('--disable-dev-shm-usage')
# 指定 ChromeDriver 的路径(根据实际情况修改)
chrome_driver_path = '/usr/bin/chromedriver'

# 创建 Chrome WebDriver 实例
driver = webdriver.Chrome(executable_path=chrome_driver_path, options=chrome_options)

# 打开网页
driver.get('https://www.google.com')

# 输出网页标题
print('Page title:', driver.title)

# 关闭浏览器
driver.quit()


运行成功的结果

python test_google.py

/usr/path/test_google.py:13: DeprecationWarning: executable_path has been deprecated, please pass in a Service object
driver = webdriver.Chrome(executable_path=chrome_driver_path, options=chrome_options)
Page title: Google



作者 east
python 9月 11,2023

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

这段代码是一个名为utils.py的模块,包含了一些工具函数和装饰器。下面是对每个函数的作用和添加的注释:

import html
import logging
import re
import time

import markdown
import inspect
from camel.messages.system_messages import SystemMessage
from online_log.app import send_msg

# 获取当前时间
def now():
    return time.strftime("%Y%m%d%H%M%S", time.localtime())

# 在线记录日志并打印信息
def log_and_print_online(role, content=None):
    if not content:
        logging.info(role + "\n")
        send_msg("System", role)
        print(role + "\n")
    else:
        print(str(role) + ": " + str(content) + "\n")
        logging.info(str(role) + ": " + str(content) + "\n")
        if isinstance(content, SystemMessage):  # 判断消息类型是否为系统消息
            records_kv = []
            content.meta_dict["content"] = content.content
            for key in content.meta_dict:
                value = content.meta_dict[key]
                value = str(value)
                value = html.unescape(value)  # 反转义HTML字符实体
                value = markdown.markdown(value)  # 将文本转换为Markdown格式
                value = re.sub(r'<[^>]*>', '', value)  # 移除HTML标签
                value = value.replace("\n", " ")  # 替换换行为空格
                records_kv.append([key, value])
            content = "**[SystemMessage**]\n\n" + convert_to_markdown_table(records_kv)
        else:
            role = str(role)
            content = str(content)
        send_msg(role, content)  # 发送消息到在线日志记录平台

# 将记录键值对列表转换为Markdown格式的表格
def convert_to_markdown_table(records_kv):
    # 创建Markdown表头
    header = "| Parameter | Value |\n| --- | --- |"

    # 创建Markdown表格行
    rows = [f"| **{key}** | {value} |" for (key, value) in records_kv]

    # 组合表头和表行形成最终的Markdown表格
    markdown_table = header + "\n" + '\n'.join(rows)

    return markdown_table

# 记录函数参数的装饰器
def log_arguments(func):
    def wrapper(*args, **kwargs):
        sig = inspect.signature(func)
        params = sig.parameters

        all_args = {}
        all_args.update({name: value for name, value in zip(params.keys(), args)})
        all_args.update(kwargs)

        records_kv = []
        for name, value in all_args.items():
            if name in ["self", "chat_env", "task_type"]:
                continue
            value = str(value)
            value = html.unescape(value)
            value = markdown.markdown(value)
            value = re.sub(r'<[^>]*>', '', value)
            value = value.replace("\n", " ")
            records_kv.append([name, value])
        records = f"**[{func.__name__}]**\n\n" + convert_to_markdown_table(records_kv)
        log_and_print_online("System", records)

        return func(*args, **kwargs)

    return wrapper

这些函数提供了一些常用的工具功能,如记录日志、发送在线消息、获取当前时间等。而装饰器log_arguments用于记录函数参数信息,将参数转换为Markdown格式的表格,并通过log_and_print_online函数将记录的信息进行在线打印和日志记录。

作者 east
python 9月 11,2023

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

这段代码是一个名为“get_info”的函数,它的作用是读取指定目录下的文件和日志信息,并计算出项目的一些基本信息,最终将这些信息以字符串的形式返回。

具体而言,这个函数接受两个参数:一个是代表目录的字符串“dir”,另一个是代表日志文件路径的字符串“log_filepath”。它首先定义了一些变量,并初始化为-1,这些变量存储的是项目信息,如代码行数、版本更新次数等等。

接下来,函数会检查目录是否存在,如果存在,则获取该目录下的所有文件名;然后统计出其中Python代码文件、PNG图像文件、其他文档文件的数量,并分别赋给相应的变量。当代码文件和PNG图像文件数量不为-1时,还会根据数量计算出费用(每个PNG文件0.016美元,每1000个提示和完成令牌各0.003美元和0.004美元,分别累计),并将计算结果加入费用“cost”的变量中。

同时,该函数还会检查目录下是否存在“meta.txt”、“requirements.txt”、“manual.md”等文件,并分别统计它们的行数,以及计算出代码文件中的代码行数,稍后都会分别保存到对应的变量中。

该函数还会读取日志文件的内容,查找指定的字符串(如“[Start Chat]”和“<->”)来获取与聊天相关的信息(如对话数量“num_utterance”和自我反思数量“num_reflection”),并累加到对应的变量中。此外,该函数还会查找日志中与GPT-3.5模型输入和输出有关的字符串,从中提取提示和完成令牌的数目、总的令牌数等信息,并分别保存到对应变量中。

最后,该函数根据从文件和日志中获取的所有信息,生成一个包含各种统计指标的字符串“info”,并将其返回。该字符串包括项目的版本、代码文件数、PNG图像文件数、其他文档文件数、代码行数、环境配置行数、手动文档行数、对话数量、自我反思数量、提示令牌数、完成令牌数、总令牌数等多个信息,每个信息都以相应的标签和数值的形式呈现。



def get_info(dir, log_filepath):
    print("dir:", dir)

    version_updates = -1
    num_code_files = -1
    num_png_files = -1
    num_doc_files = -1
    code_lines = -1
    env_lines = -1
    manual_lines = -1
    duration = -1
    num_utterance = -1
    num_reflection = -1
    num_prompt_tokens = -1
    num_completion_tokens = -1
    num_total_tokens = -1

    if os.path.exists(dir):
        filenames = os.listdir(dir)
        # print(filenames)

        num_code_files = len([filename for filename in filenames if filename.endswith(".py")])
        # print("num_code_files:", num_code_files)

        num_png_files = len([filename for filename in filenames if filename.endswith(".png")])
        # print("num_png_files:", num_png_files)

        num_doc_files = 0
        for filename in filenames:
            if filename.endswith(".py") or filename.endswith(".png"):
                continue
            if os.path.isfile(os.path.join(dir, filename)):
                # print(filename)
                num_doc_files += 1
        # print("num_doc_files:", num_doc_files)

        if "meta.txt" in filenames:
            lines = open(os.path.join(dir, "meta.txt"), "r", encoding="utf8").read().split("\n")
            version_updates = float([lines[i + 1] for i, line in enumerate(lines) if "Code_Version" in line][0]) + 1
        else:
            version_updates = -1
        # print("version_updates: ", version_updates)

        if "requirements.txt" in filenames:
            lines = open(os.path.join(dir, "requirements.txt"), "r", encoding="utf8").read().split("\n")
            env_lines = len([line for line in lines if len(line.strip()) > 0])
        else:
            env_lines = -1
        # print("env_lines:", env_lines)

        if "manual.md" in filenames:
            lines = open(os.path.join(dir, "manual.md"), "r", encoding="utf8").read().split("\n")
            manual_lines = len([line for line in lines if len(line.strip()) > 0])
        else:
            manual_lines = -1
        # print("manual_lines:", manual_lines)

        code_lines = 0
        for filename in filenames:
            if filename.endswith(".py"):
                # print("......filename:", filename)
                lines = open(os.path.join(dir, filename), "r", encoding="utf8").read().split("\n")
                code_lines += len([line for line in lines if len(line.strip()) > 0])
        # print("code_lines:", code_lines)

        lines = open(log_filepath, "r", encoding="utf8").read().split("\n")
        start_lines = [line for line in lines if "**[Start Chat]**" in line]
        chat_lines = [line for line in lines if "<->" in line]
        num_utterance = len(start_lines) + len(chat_lines)
        # print("num_utterance:", num_utterance)

        lines = open(log_filepath, "r", encoding="utf8").read().split("\n")
        sublines = [line for line in lines if line.startswith("prompt_tokens:")]
        if len(sublines) > 0:
            nums = [int(line.split(": ")[-1]) for line in sublines]
            num_prompt_tokens = np.sum(nums)
            # print("num_prompt_tokens:", num_prompt_tokens)

        lines = open(log_filepath, "r", encoding="utf8").read().split("\n")
        sublines = [line for line in lines if line.startswith("completion_tokens:")]
        if len(sublines) > 0:
            nums = [int(line.split(": ")[-1]) for line in sublines]
            num_completion_tokens = np.sum(nums)
            # print("num_completion_tokens:", num_completion_tokens)

        lines = open(log_filepath, "r", encoding="utf8").read().split("\n")
        sublines = [line for line in lines if line.startswith("total_tokens:")]
        if len(sublines) > 0:
            nums = [int(line.split(": ")[-1]) for line in sublines]
            num_total_tokens = np.sum(nums)
            # print("num_total_tokens:", num_total_tokens)

        lines = open(log_filepath, "r", encoding="utf8").read().split("\n")

        lines = open(log_filepath, "r", encoding="utf8").read().split("\n")
        num_reflection = 0
        for line in lines:
            if "on : Reflection" in line:
                num_reflection += 1
        # print("num_reflection:", num_reflection)

    cost = 0.0
    if num_png_files != -1:
        cost += num_png_files * 0.016
    if num_prompt_tokens != -1:
        cost += num_prompt_tokens * 0.003 / 1000.0
    if num_completion_tokens != -1:
        cost += num_completion_tokens * 0.004 / 1000.0

    # info = f"🕑duration={duration}s 💰cost=${cost} 🔨version_updates={version_updates} 📃num_code_files={num_code_files} 🏞num_png_files={num_png_files} 📚num_doc_files={num_doc_files} 📃code_lines={code_lines} 📋env_lines={env_lines} 📒manual_lines={manual_lines} 🗣num_utterances={num_utterance} 🤔num_self_reflections={num_reflection} ❓num_prompt_tokens={num_prompt_tokens} ❗num_completion_tokens={num_completion_tokens} ⁉️num_total_tokens={num_total_tokens}"

    info = "\n\n💰**cost**=${:.6f}\n\n🔨**version_updates**={}\n\n📃**num_code_files**={}\n\n🏞**num_png_files**={}\n\n📚**num_doc_files**={}\n\n📃**code_lines**={}\n\n📋**env_lines**={}\n\n📒**manual_lines**={}\n\n🗣**num_utterances**={}\n\n🤔**num_self_reflections**={}\n\n❓**num_prompt_tokens**={}\n\n❗**num_completion_tokens**={}\n\n🌟**num_total_tokens**={}" \
        .format(cost,
                version_updates,
                num_code_files,
                num_png_files,
                num_doc_files,
                code_lines,
                env_lines,
                manual_lines,
                num_utterance,
                num_reflection,
                num_prompt_tokens,
                num_completion_tokens,
                num_total_tokens)

    return info
作者 east
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

1 2 下一个

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

标签

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

官方QQ群

小程序开发群:74052405

大数据开发群: 952493060

近期文章

  • 详解Python当中的pip常用命令
  • AUTOSAR如何在多个供应商交付的配置中避免ARXML不兼容?
  • C++thread pool(线程池)设计应关注哪些扩展性问题?
  • 各类MCAL(Microcontroller Abstraction Layer)如何与AUTOSAR工具链解耦?
  • 如何设计AUTOSAR中的“域控制器”以支持未来扩展?
  • C++ 中避免悬挂引用的企业策略有哪些?
  • 嵌入式电机:如何在低速和高负载状态下保持FOC(Field-Oriented Control)算法的电流控制稳定?
  • C++如何在插件式架构中使用反射实现模块隔离?
  • C++如何追踪内存泄漏(valgrind/ASan等)并定位到业务代码?
  • C++大型系统中如何组织头文件和依赖树?

文章归档

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

功能

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

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