diff --git a/.github/workflows/build-package.yaml b/.github/workflows/build-package.yaml
new file mode 100644
index 000000000..7f4fee53e
--- /dev/null
+++ b/.github/workflows/build-package.yaml
@@ -0,0 +1,34 @@
+name: Build and upload python package
+
+on:
+ release:
+ types: [created]
+
+jobs:
+ deploy:
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v4
+ - name: Set up Python
+ uses: actions/setup-python@v4
+ with:
+ python-version: '3.9'
+ cache: 'pip'
+ - name: Install dependencies
+ run: |
+ python -m pip install --upgrade pip
+ pip install -r requirements.txt
+ pip install -e.
+ pip install setuptools wheel twine
+ - name: Set package version
+ run: |
+ export VERSION="${GITHUB_REF#refs/tags/v}"
+ sed -i "s/version=.*/version=\"${VERSION}\",/" setup.py
+ - name: Build and publish
+ env:
+ TWINE_USERNAME: __token__
+ TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
+ run: |
+ python setup.py bdist_wheel sdist
+ twine upload dist/*
\ No newline at end of file
diff --git a/.github/workflows/unittest.yaml b/.github/workflows/unittest.yaml
index fd56c42fb..87ccbf144 100644
--- a/.github/workflows/unittest.yaml
+++ b/.github/workflows/unittest.yaml
@@ -50,6 +50,7 @@ jobs:
run: |
export ALLOW_OPENAI_API_CALL=0
echo "${{ secrets.METAGPT_KEY_YAML }}" | base64 -d > config/key.yaml
+ mkdir -p ~/.metagpt && echo "${{ secrets.METAGPT_CONFIG2_YAML }}" | base64 -d > ~/.metagpt/config2.yaml
pytest tests/ --doctest-modules --cov=./metagpt/ --cov-report=xml:cov.xml --cov-report=html:htmlcov --durations=20 | tee unittest.txt
- name: Show coverage report
run: |
diff --git a/.gitignore b/.gitignore
index a69b3b1c2..4752806d7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -176,5 +176,6 @@ htmlcov.*
cov.xml
*.dot
*.pkl
+*.faiss
*-structure.csv
*-structure.json
diff --git a/README.md b/README.md
index 9c88c92a1..90c586068 100644
--- a/README.md
+++ b/README.md
@@ -6,16 +6,16 @@ # MetaGPT: The Multi-Agent Framework
-Assign different roles to GPTs to form a collaborative software entity for complex tasks.
+Assign different roles to GPTs to form a collaborative entity for complex tasks.
-
+
@@ -25,19 +25,31 @@ # MetaGPT: The Multi-Agent Framework
+## News
+🚀 Jan. 16, 2024: Our paper [MetaGPT: Meta Programming for A Multi-Agent Collaborative Framework
+](https://arxiv.org/abs/2308.00352) accepted for oral presentation **(top 1.2%)** at ICLR 2024, **ranking #1** in the LLM-based Agent category.
+
+🚀 Jan. 03, 2024: [v0.6.0](https://github.com/geekan/MetaGPT/releases/tag/v0.6.0) released, new features include serialization, upgraded OpenAI package and supported multiple LLM, provided [minimal example for debate](https://github.com/geekan/MetaGPT/blob/main/examples/debate_simple.py) etc.
+
+🚀 Dec. 15, 2023: [v0.5.0](https://github.com/geekan/MetaGPT/releases/tag/v0.5.0) released, introducing some experimental features such as **incremental development**, **multilingual**, **multiple programming languages**, etc.
+
+🔥 Nov. 08, 2023: MetaGPT is selected into [Open100: Top 100 Open Source achievements](https://www.benchcouncil.org/evaluation/opencs/annual.html).
+
+🔥 Sep. 01, 2023: MetaGPT tops GitHub Trending Monthly for the **17th time** in August 2023.
+
+🌟 Jun. 30, 2023: MetaGPT is now open source.
+
+🌟 Apr. 24, 2023: First line of MetaGPT code committed.
+
+## Software Company as Multi-Agent System
+
1. MetaGPT takes a **one line requirement** as input and outputs **user stories / competitive analysis / requirements / data structures / APIs / documents, etc.**
2. Internally, MetaGPT includes **product managers / architects / project managers / engineers.** It provides the entire process of a **software company along with carefully orchestrated SOPs.**
1. `Code = SOP(Team)` is the core philosophy. We materialize SOP and apply it to teams composed of LLMs.

-Software Company Multi-Role Schematic (Gradually Implementing)
-
-## News
-🚀 Jan 03: Here comes [v0.6.0](https://github.com/geekan/MetaGPT/releases/tag/v0.6.0)! In this version, we added serialization and deserialization of important objects and enabled breakpoint recovery. We upgraded OpenAI package to v1.6.0 and supported Gemini, ZhipuAI, Ollama, OpenLLM, etc. Moreover, we provided extremely simple examples where you need only 7 lines to implement a general election [debate](https://github.com/geekan/MetaGPT/blob/main/examples/debate_simple.py). Check out more details [here](https://github.com/geekan/MetaGPT/releases/tag/v0.6.0)!
-
-
-🚀 Dec 15: [v0.5.0](https://github.com/geekan/MetaGPT/releases/tag/v0.5.0) is released! We introduced **incremental development**, facilitating agents to build up larger projects on top of their previous efforts or existing codebase. We also launched a whole collection of important features, including **multilingual support** (experimental), multiple **programming languages support** (experimental), **incremental development** (experimental), CLI support, pip support, enhanced code review, documentation mechanism, and optimized messaging mechanism!
+Software Company Multi-Agent Schematic (Gradually Implementing)
## Install
diff --git a/config/config.yaml b/config/config.yaml
deleted file mode 100644
index d8fab693e..000000000
--- a/config/config.yaml
+++ /dev/null
@@ -1,149 +0,0 @@
-# DO NOT MODIFY THIS FILE, create a new key.yaml, define OPENAI_API_KEY.
-# The configuration of key.yaml has a higher priority and will not enter git
-
-#### Project Path Setting
-# WORKSPACE_PATH: "Path for placing output files"
-
-#### if OpenAI
-## The official OPENAI_BASE_URL is https://api.openai.com/v1
-## If the official OPENAI_BASE_URL is not available, we recommend using the [openai-forward](https://github.com/beidongjiedeguang/openai-forward).
-## Or, you can configure OPENAI_PROXY to access official OPENAI_BASE_URL.
-OPENAI_BASE_URL: "https://api.openai.com/v1"
-#OPENAI_PROXY: "http://127.0.0.1:8118"
-#OPENAI_API_KEY: "YOUR_API_KEY" # set the value to sk-xxx if you host the openai interface for open llm model
-OPENAI_API_MODEL: "gpt-4-1106-preview"
-MAX_TOKENS: 4096
-RPM: 10
-TIMEOUT: 60 # Timeout for llm invocation
-#DEFAULT_PROVIDER: openai
-
-#### if Spark
-#SPARK_APPID : "YOUR_APPID"
-#SPARK_API_SECRET : "YOUR_APISecret"
-#SPARK_API_KEY : "YOUR_APIKey"
-#DOMAIN : "generalv2"
-#SPARK_URL : "ws://spark-api.xf-yun.com/v2.1/chat"
-
-#### if Anthropic
-#ANTHROPIC_API_KEY: "YOUR_API_KEY"
-
-#### if AZURE, check https://github.com/openai/openai-cookbook/blob/main/examples/azure/chat.ipynb
-#OPENAI_API_TYPE: "azure"
-#OPENAI_BASE_URL: "YOUR_AZURE_ENDPOINT"
-#OPENAI_API_KEY: "YOUR_AZURE_API_KEY"
-#OPENAI_API_VERSION: "YOUR_AZURE_API_VERSION"
-#DEPLOYMENT_NAME: "YOUR_DEPLOYMENT_NAME"
-
-#### if zhipuai from `https://open.bigmodel.cn`. You can set here or export API_KEY="YOUR_API_KEY"
-# ZHIPUAI_API_KEY: "YOUR_API_KEY"
-
-#### if Google Gemini from `https://ai.google.dev/` and API_KEY from `https://makersuite.google.com/app/apikey`.
-#### You can set here or export GOOGLE_API_KEY="YOUR_API_KEY"
-# GEMINI_API_KEY: "YOUR_API_KEY"
-
-#### if use self-host open llm model with openai-compatible interface
-#OPEN_LLM_API_BASE: "http://127.0.0.1:8000/v1"
-#OPEN_LLM_API_MODEL: "llama2-13b"
-#
-##### if use Fireworks api
-#FIREWORKS_API_KEY: "YOUR_API_KEY"
-#FIREWORKS_API_BASE: "https://api.fireworks.ai/inference/v1"
-#FIREWORKS_API_MODEL: "YOUR_LLM_MODEL" # example, accounts/fireworks/models/llama-v2-13b-chat
-
-#### if use self-host open llm model by ollama
-# OLLAMA_API_BASE: http://127.0.0.1:11434/api
-# OLLAMA_API_MODEL: llama2
-
-#### for Search
-
-## Supported values: serpapi/google/serper/ddg
-#SEARCH_ENGINE: serpapi
-
-## Visit https://serpapi.com/ to get key.
-#SERPAPI_API_KEY: "YOUR_API_KEY"
-
-## Visit https://console.cloud.google.com/apis/credentials to get key.
-#GOOGLE_API_KEY: "YOUR_API_KEY"
-## Visit https://programmablesearchengine.google.com/controlpanel/create to get id.
-#GOOGLE_CSE_ID: "YOUR_CSE_ID"
-
-## Visit https://serper.dev/ to get key.
-#SERPER_API_KEY: "YOUR_API_KEY"
-
-#### for web access
-
-## Supported values: playwright/selenium
-#WEB_BROWSER_ENGINE: playwright
-
-## Supported values: chromium/firefox/webkit, visit https://playwright.dev/python/docs/api/class-browsertype
-##PLAYWRIGHT_BROWSER_TYPE: chromium
-
-## Supported values: chrome/firefox/edge/ie, visit https://www.selenium.dev/documentation/webdriver/browsers/
-# SELENIUM_BROWSER_TYPE: chrome
-
-#### for TTS
-
-#AZURE_TTS_SUBSCRIPTION_KEY: "YOUR_API_KEY"
-#AZURE_TTS_REGION: "eastus"
-
-#### for OPENAI VISION
-
-#OPENAI_VISION_MODEL: "YOUR_VISION_MODEL_NAME"
-#VISION_MAX_TOKENS: 4096
-
-#### for Stable Diffusion
-## Use SD service, based on https://github.com/AUTOMATIC1111/stable-diffusion-webui
-#SD_URL: "YOUR_SD_URL"
-#SD_T2I_API: "/sdapi/v1/txt2img"
-
-#### for Execution
-#LONG_TERM_MEMORY: false
-
-#### for Mermaid CLI
-## If you installed mmdc (Mermaid CLI) only for metagpt then enable the following configuration.
-#PUPPETEER_CONFIG: "./config/puppeteer-config.json"
-#MMDC: "./node_modules/.bin/mmdc"
-
-
-### for calc_usage
-# CALC_USAGE: false
-
-### for Research
-# MODEL_FOR_RESEARCHER_SUMMARY: gpt-3.5-turbo
-# MODEL_FOR_RESEARCHER_REPORT: gpt-3.5-turbo-16k
-
-### choose the engine for mermaid conversion,
-# default is nodejs, you can change it to playwright,pyppeteer or ink
-# MERMAID_ENGINE: nodejs
-
-### browser path for pyppeteer engine, support Chrome, Chromium,MS Edge
-#PYPPETEER_EXECUTABLE_PATH: "/usr/bin/google-chrome-stable"
-
-### for repair non-openai LLM's output when parse json-text if PROMPT_FORMAT=json
-### due to non-openai LLM's output will not always follow the instruction, so here activate a post-process
-### repair operation on the content extracted from LLM's raw output. Warning, it improves the result but not fix all cases.
-# REPAIR_LLM_OUTPUT: false
-
-# PROMPT_FORMAT: json #json or markdown
-
-### Agent configurations
-# RAISE_NOT_CONFIG_ERROR: true # "true" if the LLM key is not configured, throw a NotConfiguredException, else "false".
-# WORKSPACE_PATH_WITH_UID: false # "true" if using `{workspace}/{uid}` as the workspace path; "false" use `{workspace}`.
-
-### Meta Models
-#METAGPT_TEXT_TO_IMAGE_MODEL: MODEL_URL
-
-### S3 config
-#S3_ACCESS_KEY: "YOUR_S3_ACCESS_KEY"
-#S3_SECRET_KEY: "YOUR_S3_SECRET_KEY"
-#S3_ENDPOINT_URL: "YOUR_S3_ENDPOINT_URL"
-#S3_SECURE: true # true/false
-#S3_BUCKET: "YOUR_S3_BUCKET"
-
-### Redis config
-#REDIS_HOST: "YOUR_REDIS_HOST"
-#REDIS_PORT: "YOUR_REDIS_PORT"
-#REDIS_PASSWORD: "YOUR_REDIS_PASSWORD"
-#REDIS_DB: "YOUR_REDIS_DB_INDEX, str, 0-based"
-
-# DISABLE_LLM_PROVIDER_CHECK: false
diff --git a/config/config2.yaml b/config/config2.yaml
new file mode 100644
index 000000000..5e7f34809
--- /dev/null
+++ b/config/config2.yaml
@@ -0,0 +1,3 @@
+llm:
+ api_key: "YOUR_API_KEY"
+ model: "gpt-3.5-turbo-1106"
\ No newline at end of file
diff --git a/config/config2.yaml.example b/config/config2.yaml.example
new file mode 100644
index 000000000..35575e5a5
--- /dev/null
+++ b/config/config2.yaml.example
@@ -0,0 +1,42 @@
+llm:
+ api_type: "openai"
+ base_url: "YOUR_BASE_URL"
+ api_key: "YOUR_API_KEY"
+ model: "gpt-3.5-turbo-1106" # or gpt-4-1106-preview
+
+proxy: "YOUR_PROXY"
+
+search:
+ api_type: "google"
+ api_key: "YOUR_API_KEY"
+ cse_id: "YOUR_CSE_ID"
+
+mermaid:
+ engine: "pyppeteer"
+ path: "/Applications/Google Chrome.app"
+
+redis:
+ host: "YOUR_HOST"
+ port: 32582
+ password: "YOUR_PASSWORD"
+ db: "0"
+
+s3:
+ access_key: "YOUR_ACCESS_KEY"
+ secret_key: "YOUR_SECRET_KEY"
+ endpoint: "YOUR_ENDPOINT"
+ secure: false
+ bucket: "test"
+
+
+AZURE_TTS_SUBSCRIPTION_KEY: "YOUR_SUBSCRIPTION_KEY"
+AZURE_TTS_REGION: "eastus"
+
+IFLYTEK_APP_ID: "YOUR_APP_ID"
+IFLYTEK_API_KEY: "YOUR_API_KEY"
+IFLYTEK_API_SECRET: "YOUR_API_SECRET"
+
+METAGPT_TEXT_TO_IMAGE_MODEL_URL: "YOUR_MODEL_URL"
+
+PYPPETEER_EXECUTABLE_PATH: "/Applications/Google Chrome.app"
+
diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md
index d3f7ea408..4bb530bf2 100644
--- a/docs/ROADMAP.md
+++ b/docs/ROADMAP.md
@@ -9,24 +9,22 @@ ### Short-term Objective
1. Become the multi-agent framework with the highest ROI.
2. Support fully automatic implementation of medium-sized projects (around 2000 lines of code).
-3. Implement most identified tasks, reaching version 0.5.
+3. Implement most identified tasks, reaching version 1.0.
### Tasks
-To reach version v0.5, approximately 70% of the following tasks need to be completed.
-
1. Usability
1. ~~Release v0.01 pip package to try to solve issues like npm installation (though not necessarily successfully)~~ (v0.3.0)
- 2. Support for overall save and recovery of software companies
+ 2. ~~Support for overall save and recovery of software companies~~ (v0.6.0)
3. ~~Support human confirmation and modification during the process~~ (v0.3.0) New: Support human confirmation and modification with fewer constrainsts and a more user-friendly interface
4. Support process caching: Consider carefully whether to add server caching mechanism
5. ~~Resolve occasional failure to follow instruction under current prompts, causing code parsing errors, through stricter system prompts~~ (v0.4.0, with function call)
6. Write documentation, describing the current features and usage at all levels (ongoing, continuously adding contents to [documentation site](https://docs.deepwisdom.ai/main/en/guide/get_started/introduction.html))
7. ~~Support Docker~~
2. Features
- 1. Support a more standard and stable parser (need to analyze the format that the current LLM is better at)
- 2. ~~Establish a separate output queue, differentiated from the message queue~~
- 3. Attempt to atomize all role work, but this may significantly increase token overhead
+ 1. ~~Support a more standard and stable parser (need to analyze the format that the current LLM is better at)~~ (v0.5.0)
+ 2. ~~Establish a separate output queue, differentiated from the message queue~~ (v0.5.0)
+ 3. ~~Attempt to atomize all role work, but this may significantly increase token overhead~~ (v0.5.0)
4. Complete the design and implementation of module breakdown
5. Support various modes of memory: clearly distinguish between long-term and short-term memory
6. Perfect the test role, and carry out necessary interactions with humans
@@ -43,10 +41,10 @@ ### Tasks
4. Actions
1. ~~Implementation: Search~~ (v0.2.1)
2. Implementation: Knowledge search, supporting 10+ data formats
- 3. Implementation: Data EDA (expected v0.6.0)
- 4. Implementation: Review
- 5. ~~Implementation~~: Add Document (v0.5.0)
- 6. ~~Implementation~~: Delete Document (v0.5.0)
+ 3. Implementation: Data EDA (expected v0.7.0)
+ 4. Implementation: Review & Revise (expected v0.7.0)
+ 5. ~~Implementation: Add Document~~ (v0.5.0)
+ 6. ~~Implementation: Delete Document~~ (v0.5.0)
7. Implementation: Self-training
8. ~~Implementation: DebugError~~ (v0.2.1)
9. Implementation: Generate reliable unit tests based on YAPI
@@ -64,15 +62,14 @@ ### Tasks
3. ~~Support Playwright apis~~
7. Roles
1. Perfect the action pool/skill pool for each role
- 2. Red Book blogger
- 3. E-commerce seller
- 4. Data analyst (expected v0.6.0)
- 5. News observer
- 6. ~~Institutional researcher~~ (v0.2.1)
+ 2. E-commerce seller
+ 3. Data analyst (expected v0.7.0)
+ 4. News observer
+ 5. ~~Institutional researcher~~ (v0.2.1)
8. Evaluation
1. Support an evaluation on a game dataset (experimentation done with game agents)
2. Reproduce papers, implement full skill acquisition for a single game role, achieving SOTA results (experimentation done with game agents)
- 3. Support an evaluation on a math dataset (expected v0.6.0)
+ 3. Support an evaluation on a math dataset (expected v0.7.0)
4. Reproduce papers, achieving SOTA results for current mathematical problem solving process
9. LLM
1. Support Claude underlying API
@@ -80,7 +77,7 @@ ### Tasks
3. Support streaming version of all APIs
4. ~~Make gpt-3.5-turbo available (HARD)~~
10. Other
- 1. Clean up existing unused code
- 2. Unify all code styles and establish contribution standards
- 3. Multi-language support
- 4. Multi-programming-language support
+ 1. ~~Clean up existing unused code~~
+ 2. ~~Unify all code styles and establish contribution standards~~
+ 3. ~~Multi-language support~~
+ 4. ~~Multi-programming-language support~~
diff --git a/examples/agent_creator.py b/examples/agent_creator.py
index 340dfafa4..bd58840ce 100644
--- a/examples/agent_creator.py
+++ b/examples/agent_creator.py
@@ -6,7 +6,7 @@ Author: garylin2099
import re
from metagpt.actions import Action
-from metagpt.config import CONFIG
+from metagpt.config2 import config
from metagpt.const import METAGPT_ROOT
from metagpt.logs import logger
from metagpt.roles import Role
@@ -48,8 +48,8 @@ class CreateAgent(Action):
pattern = r"```python(.*)```"
match = re.search(pattern, rsp, re.DOTALL)
code_text = match.group(1) if match else ""
- CONFIG.workspace_path.mkdir(parents=True, exist_ok=True)
- new_file = CONFIG.workspace_path / "agent_created_agent.py"
+ config.workspace.path.mkdir(parents=True, exist_ok=True)
+ new_file = config.workspace.path / "agent_created_agent.py"
new_file.write_text(code_text)
return code_text
@@ -61,7 +61,7 @@ class AgentCreator(Role):
def __init__(self, **kwargs):
super().__init__(**kwargs)
- self._init_actions([CreateAgent])
+ self.set_actions([CreateAgent])
async def _act(self) -> Message:
logger.info(f"{self._setting}: to do {self.rc.todo}({self.rc.todo.name})")
diff --git a/examples/build_customized_agent.py b/examples/build_customized_agent.py
index 6c3219efc..cfe264b47 100644
--- a/examples/build_customized_agent.py
+++ b/examples/build_customized_agent.py
@@ -57,7 +57,7 @@ class SimpleCoder(Role):
def __init__(self, **kwargs):
super().__init__(**kwargs)
- self._init_actions([SimpleWriteCode])
+ self.set_actions([SimpleWriteCode])
async def _act(self) -> Message:
logger.info(f"{self._setting}: to do {self.rc.todo}({self.rc.todo.name})")
@@ -76,7 +76,7 @@ class RunnableCoder(Role):
def __init__(self, **kwargs):
super().__init__(**kwargs)
- self._init_actions([SimpleWriteCode, SimpleRunCode])
+ self.set_actions([SimpleWriteCode, SimpleRunCode])
self._set_react_mode(react_mode=RoleReactMode.BY_ORDER.value)
async def _act(self) -> Message:
diff --git a/examples/build_customized_multi_agents.py b/examples/build_customized_multi_agents.py
index 73278c08c..296323cea 100644
--- a/examples/build_customized_multi_agents.py
+++ b/examples/build_customized_multi_agents.py
@@ -46,7 +46,7 @@ class SimpleCoder(Role):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self._watch([UserRequirement])
- self._init_actions([SimpleWriteCode])
+ self.set_actions([SimpleWriteCode])
class SimpleWriteTest(Action):
@@ -75,7 +75,7 @@ class SimpleTester(Role):
def __init__(self, **kwargs):
super().__init__(**kwargs)
- self._init_actions([SimpleWriteTest])
+ self.set_actions([SimpleWriteTest])
# self._watch([SimpleWriteCode])
self._watch([SimpleWriteCode, SimpleWriteReview]) # feel free to try this too
@@ -114,7 +114,7 @@ class SimpleReviewer(Role):
def __init__(self, **kwargs):
super().__init__(**kwargs)
- self._init_actions([SimpleWriteReview])
+ self.set_actions([SimpleWriteReview])
self._watch([SimpleWriteTest])
diff --git a/examples/crawl_webpage.py b/examples/crawl_webpage.py
new file mode 100644
index 000000000..35413d2ff
--- /dev/null
+++ b/examples/crawl_webpage.py
@@ -0,0 +1,22 @@
+# -*- encoding: utf-8 -*-
+"""
+@Date : 2024/01/24 15:11:27
+@Author : orange-crow
+@File : crawl_webpage.py
+"""
+
+from metagpt.roles.code_interpreter import CodeInterpreter
+
+
+async def main():
+ prompt = """Get data from `paperlist` table in https://papercopilot.com/statistics/iclr-statistics/iclr-2024-statistics/,
+ and save it to a csv file. paper title must include `multiagent` or `large language model`. *notice: print key data*"""
+ ci = CodeInterpreter(goal=prompt, use_tools=True)
+
+ await ci.run(prompt)
+
+
+if __name__ == "__main__":
+ import asyncio
+
+ asyncio.run(main())
diff --git a/examples/debate.py b/examples/debate.py
index eb0a09839..72ab8796d 100644
--- a/examples/debate.py
+++ b/examples/debate.py
@@ -49,7 +49,7 @@ class Debator(Role):
def __init__(self, **data: Any):
super().__init__(**data)
- self._init_actions([SpeakAloud])
+ self.set_actions([SpeakAloud])
self._watch([UserRequirement, SpeakAloud])
async def _observe(self) -> int:
diff --git a/examples/debate_simple.py b/examples/debate_simple.py
index aa95c5b85..869e02a0e 100644
--- a/examples/debate_simple.py
+++ b/examples/debate_simple.py
@@ -13,7 +13,9 @@ from metagpt.roles import Role
from metagpt.team import Team
action1 = Action(name="AlexSay", instruction="Express your opinion with emotion and don't repeat it")
+action1.llm.model = "gpt-4-1106-preview"
action2 = Action(name="BobSay", instruction="Express your opinion with emotion and don't repeat it")
+action2.llm.model = "gpt-3.5-turbo-1106"
alex = Role(name="Alex", profile="Democratic candidate", goal="Win the election", actions=[action1], watch=[action2])
bob = Role(name="Bob", profile="Republican candidate", goal="Win the election", actions=[action2], watch=[action1])
env = Environment(desc="US election live broadcast")
diff --git a/examples/example.faiss b/examples/example.faiss
deleted file mode 100644
index 580946190..000000000
Binary files a/examples/example.faiss and /dev/null differ
diff --git a/examples/example.pkl b/examples/example.pkl
deleted file mode 100644
index f706fd803..000000000
Binary files a/examples/example.pkl and /dev/null differ
diff --git a/examples/llm_hello_world.py b/examples/llm_hello_world.py
index 76be1cc90..219a303c8 100644
--- a/examples/llm_hello_world.py
+++ b/examples/llm_hello_world.py
@@ -23,6 +23,10 @@ async def main():
# streaming mode, much slower
await llm.acompletion_text(hello_msg, stream=True)
+ # check completion if exist to test llm complete functions
+ if hasattr(llm, "completion"):
+ logger.info(llm.completion(hello_msg))
+
if __name__ == "__main__":
asyncio.run(main())
diff --git a/examples/search_kb.py b/examples/search_kb.py
index 0e0e0ffd0..995720cc1 100644
--- a/examples/search_kb.py
+++ b/examples/search_kb.py
@@ -8,7 +8,7 @@ import asyncio
from langchain.embeddings import OpenAIEmbeddings
-from metagpt.config import CONFIG
+from metagpt.config2 import config
from metagpt.const import DATA_PATH, EXAMPLE_PATH
from metagpt.document_store import FaissStore
from metagpt.logs import logger
@@ -16,7 +16,8 @@ from metagpt.roles import Sales
def get_store():
- embedding = OpenAIEmbeddings(openai_api_key=CONFIG.openai_api_key, openai_api_base=CONFIG.openai_base_url)
+ llm = config.get_openai_llm()
+ embedding = OpenAIEmbeddings(openai_api_key=llm.api_key, openai_api_base=llm.base_url)
return FaissStore(DATA_PATH / "example.json", embedding=embedding)
diff --git a/metagpt/actions/action.py b/metagpt/actions/action.py
index b586bcc22..1b93213f7 100644
--- a/metagpt/actions/action.py
+++ b/metagpt/actions/action.py
@@ -10,41 +10,67 @@ from __future__ import annotations
from typing import Optional, Union
-from pydantic import ConfigDict, Field, model_validator
+from pydantic import BaseModel, ConfigDict, Field, model_validator
from metagpt.actions.action_node import ActionNode
-from metagpt.llm import LLM
-from metagpt.provider.base_llm import BaseLLM
+from metagpt.context_mixin import ContextMixin
from metagpt.schema import (
+ CodePlanAndChangeContext,
CodeSummarizeContext,
CodingContext,
RunCodeContext,
SerializationMixin,
TestingContext,
)
+from metagpt.utils.project_repo import ProjectRepo
-class Action(SerializationMixin, is_polymorphic_base=True):
- model_config = ConfigDict(arbitrary_types_allowed=True, exclude=["llm"])
+class Action(SerializationMixin, ContextMixin, BaseModel):
+ model_config = ConfigDict(arbitrary_types_allowed=True)
name: str = ""
- llm: BaseLLM = Field(default_factory=LLM, exclude=True)
- context: Union[dict, CodingContext, CodeSummarizeContext, TestingContext, RunCodeContext, str, None] = ""
+ i_context: Union[
+ dict, CodingContext, CodeSummarizeContext, TestingContext, RunCodeContext, CodePlanAndChangeContext, str, None
+ ] = ""
prefix: str = "" # aask*时会加上prefix,作为system_message
desc: str = "" # for skill manager
node: ActionNode = Field(default=None, exclude=True)
+ @property
+ def repo(self) -> ProjectRepo:
+ if not self.context.repo:
+ self.context.repo = ProjectRepo(self.context.git_repo)
+ return self.context.repo
+
+ @property
+ def prompt_schema(self):
+ return self.config.prompt_schema
+
+ @property
+ def project_name(self):
+ return self.config.project_name
+
+ @project_name.setter
+ def project_name(self, value):
+ self.config.project_name = value
+
+ @property
+ def project_path(self):
+ return self.config.project_path
+
@model_validator(mode="before")
+ @classmethod
def set_name_if_empty(cls, values):
if "name" not in values or not values["name"]:
values["name"] = cls.__name__
return values
@model_validator(mode="before")
+ @classmethod
def _init_with_instruction(cls, values):
if "instruction" in values:
name = values["name"]
- i = values["instruction"]
+ i = values.pop("instruction")
values["node"] = ActionNode(key=name, expected_type=str, instruction=i, example="", schema="raw")
return values
diff --git a/metagpt/actions/action_node.py b/metagpt/actions/action_node.py
index 6c65b33ef..162ab90eb 100644
--- a/metagpt/actions/action_node.py
+++ b/metagpt/actions/action_node.py
@@ -9,16 +9,30 @@ NOTE: You should use typing.List instead of list to do type annotation. Because
we can use typing to extract the type of the node, but we cannot use built-in list to extract.
"""
import json
-from typing import Any, Dict, List, Optional, Tuple, Type
+from enum import Enum
+from typing import Any, Dict, List, Optional, Tuple, Type, Union
-from pydantic import BaseModel, create_model, model_validator
+from pydantic import BaseModel, Field, create_model, model_validator
from tenacity import retry, stop_after_attempt, wait_random_exponential
-from metagpt.config import CONFIG
+from metagpt.actions.action_outcls_registry import register_action_outcls
from metagpt.llm import BaseLLM
from metagpt.logs import logger
from metagpt.provider.postprocess.llm_output_postprocess import llm_output_postprocess
from metagpt.utils.common import OutputParser, general_after_log
+from metagpt.utils.human_interaction import HumanInteraction
+
+
+class ReviewMode(Enum):
+ HUMAN = "human"
+ AUTO = "auto"
+
+
+class ReviseMode(Enum):
+ HUMAN = "human" # human revise
+ HUMAN_REVIEW = "human_review" # human-review and auto-revise
+ AUTO = "auto" # auto-review and auto-revise
+
TAG = "CONTENT"
@@ -45,6 +59,58 @@ SIMPLE_TEMPLATE = """
Follow instructions of nodes, generate output and make sure it follows the format example.
"""
+REVIEW_TEMPLATE = """
+## context
+Compare the key's value of nodes_output and the corresponding requirements one by one. If a key's value that does not match the requirement is found, provide the comment content on how to modify it. No output is required for matching keys.
+
+### nodes_output
+{nodes_output}
+
+-----
+
+## format example
+[{tag}]
+{{
+ "key1": "comment1",
+ "key2": "comment2",
+ "keyn": "commentn"
+}}
+[/{tag}]
+
+## nodes: ": # "
+- key1: # the first key name of mismatch key
+- key2: # the second key name of mismatch key
+- keyn: # the last key name of mismatch key
+
+## constraint
+{constraint}
+
+## action
+Follow format example's {prompt_schema} format, generate output and make sure it follows the format example.
+"""
+
+REVISE_TEMPLATE = """
+## context
+change the nodes_output key's value to meet its comment and no need to add extra comment.
+
+### nodes_output
+{nodes_output}
+
+-----
+
+## format example
+{example}
+
+## nodes: ": # "
+{instruction}
+
+## constraint
+{constraint}
+
+## action
+Follow format example's {prompt_schema} format, generate output and make sure it follows the format example.
+"""
+
def dict_to_markdown(d, prefix="- ", kv_sep="\n", postfix="\n"):
markdown_str = ""
@@ -105,6 +171,9 @@ class ActionNode:
"""增加子ActionNode"""
self.children[node.key] = node
+ def get_child(self, key: str) -> Union["ActionNode", None]:
+ return self.children.get(key, None)
+
def add_children(self, nodes: List["ActionNode"]):
"""批量增加子ActionNode"""
for node in nodes:
@@ -117,11 +186,27 @@ class ActionNode:
obj.add_children(nodes)
return obj
- def get_children_mapping(self, exclude=None) -> Dict[str, Tuple[Type, Any]]:
+ def get_children_mapping_old(self, exclude=None) -> Dict[str, Tuple[Type, Any]]:
"""获得子ActionNode的字典,以key索引"""
exclude = exclude or []
return {k: (v.expected_type, ...) for k, v in self.children.items() if k not in exclude}
+ def get_children_mapping(self, exclude=None) -> Dict[str, Tuple[Type, Any]]:
+ """获得子ActionNode的字典,以key索引,支持多级结构"""
+ exclude = exclude or []
+ mapping = {}
+
+ def _get_mapping(node: "ActionNode", prefix: str = ""):
+ for key, child in node.children.items():
+ if key in exclude:
+ continue
+ full_key = f"{prefix}{key}"
+ mapping[full_key] = (child.expected_type, ...)
+ _get_mapping(child, prefix=f"{full_key}.")
+
+ _get_mapping(self)
+ return mapping
+
def get_self_mapping(self) -> Dict[str, Tuple[Type, Any]]:
"""get self key: type mapping"""
return {self.key: (self.expected_type, ...)}
@@ -133,6 +218,7 @@ class ActionNode:
return {} if exclude and self.key in exclude else self.get_self_mapping()
@classmethod
+ @register_action_outcls
def create_model_class(cls, class_name: str, mapping: Dict[str, Tuple[Type, Any]]):
"""基于pydantic v1的模型动态生成,用来检验结果类型正确性"""
@@ -152,6 +238,11 @@ class ActionNode:
new_class = create_model(class_name, __validators__=validators, **mapping)
return new_class
+ def create_class(self, mode: str = "auto", class_name: str = None, exclude=None):
+ class_name = class_name if class_name else f"{self.key}_AN"
+ mapping = self.get_mapping(mode=mode, exclude=exclude)
+ return self.create_model_class(class_name, mapping)
+
def create_children_class(self, exclude=None):
"""使用object内有的字段直接生成model_class"""
class_name = f"{self.key}_AN"
@@ -186,6 +277,25 @@ class ActionNode:
return node_dict
+ def update_instruct_content(self, incre_data: dict[str, Any]):
+ assert self.instruct_content
+ origin_sc_dict = self.instruct_content.model_dump()
+ origin_sc_dict.update(incre_data)
+ output_class = self.create_class()
+ self.instruct_content = output_class(**origin_sc_dict)
+
+ def keys(self, mode: str = "auto") -> list:
+ if mode == "children" or (mode == "auto" and self.children):
+ keys = []
+ else:
+ keys = [self.key]
+ if mode == "root":
+ return keys
+
+ for _, child_node in self.children.items():
+ keys.append(child_node.key)
+ return keys
+
def compile_to(self, i: Dict, schema, kv_sep) -> str:
if schema == "json":
return json.dumps(i, indent=4)
@@ -262,7 +372,7 @@ class ActionNode:
output_data_mapping: dict,
system_msgs: Optional[list[str]] = None,
schema="markdown", # compatible to original format
- timeout=CONFIG.timeout,
+ timeout=3,
) -> (str, BaseModel):
"""Use ActionOutput to wrap the output of aask"""
content = await self.llm.aask(prompt, system_msgs, timeout=timeout)
@@ -294,7 +404,7 @@ class ActionNode:
def set_context(self, context):
self.set_recursive("context", context)
- async def simple_fill(self, schema, mode, timeout=CONFIG.timeout, exclude=None):
+ async def simple_fill(self, schema, mode, timeout=3, exclude=None):
prompt = self.compile(context=self.context, schema=schema, mode=mode, exclude=exclude)
if schema != "raw":
@@ -309,7 +419,7 @@ class ActionNode:
return self
- async def fill(self, context, llm, schema="json", mode="auto", strgy="simple", timeout=CONFIG.timeout, exclude=[]):
+ async def fill(self, context, llm, schema="json", mode="auto", strgy="simple", timeout=3, exclude=[]):
"""Fill the node(s) with mode.
:param context: Everything we should know when filling node.
@@ -343,7 +453,241 @@ class ActionNode:
if exclude and i.key in exclude:
continue
child = await i.simple_fill(schema=schema, mode=mode, timeout=timeout, exclude=exclude)
- tmp.update(child.instruct_content.dict())
+ tmp.update(child.instruct_content.model_dump())
cls = self.create_children_class()
self.instruct_content = cls(**tmp)
return self
+
+ async def human_review(self) -> dict[str, str]:
+ review_comments = HumanInteraction().interact_with_instruct_content(
+ instruct_content=self.instruct_content, interact_type="review"
+ )
+
+ return review_comments
+
+ def _makeup_nodes_output_with_req(self) -> dict[str, str]:
+ instruct_content_dict = self.instruct_content.model_dump()
+ nodes_output = {}
+ for key, value in instruct_content_dict.items():
+ child = self.get_child(key)
+ nodes_output[key] = {"value": value, "requirement": child.instruction if child else self.instruction}
+ return nodes_output
+
+ async def auto_review(self, template: str = REVIEW_TEMPLATE) -> dict[str, str]:
+ """use key's output value and its instruction to review the modification comment"""
+ nodes_output = self._makeup_nodes_output_with_req()
+ """nodes_output format:
+ {
+ "key": {"value": "output value", "requirement": "key instruction"}
+ }
+ """
+ if not nodes_output:
+ return dict()
+
+ prompt = template.format(
+ nodes_output=json.dumps(nodes_output, ensure_ascii=False),
+ tag=TAG,
+ constraint=FORMAT_CONSTRAINT,
+ prompt_schema="json",
+ )
+
+ content = await self.llm.aask(prompt)
+ # Extract the dict of mismatch key and its comment. Due to the mismatch keys are unknown, here use the keys
+ # of ActionNode to judge if exist in `content` and then follow the `data_mapping` method to create model class.
+ keys = self.keys()
+ include_keys = []
+ for key in keys:
+ if f'"{key}":' in content:
+ include_keys.append(key)
+ if not include_keys:
+ return dict()
+
+ exclude_keys = list(set(keys).difference(include_keys))
+ output_class_name = f"{self.key}_AN_REVIEW"
+ output_class = self.create_class(class_name=output_class_name, exclude=exclude_keys)
+ parsed_data = llm_output_postprocess(
+ output=content, schema=output_class.model_json_schema(), req_key=f"[/{TAG}]"
+ )
+ instruct_content = output_class(**parsed_data)
+ return instruct_content.model_dump()
+
+ async def simple_review(self, review_mode: ReviewMode = ReviewMode.AUTO):
+ # generate review comments
+ if review_mode == ReviewMode.HUMAN:
+ review_comments = await self.human_review()
+ else:
+ review_comments = await self.auto_review()
+
+ if not review_comments:
+ logger.warning("There are no review comments")
+ return review_comments
+
+ async def review(self, strgy: str = "simple", review_mode: ReviewMode = ReviewMode.AUTO):
+ """only give the review comment of each exist and mismatch key
+
+ :param strgy: simple/complex
+ - simple: run only once
+ - complex: run each node
+ """
+ if not hasattr(self, "llm"):
+ raise RuntimeError("use `review` after `fill`")
+ assert review_mode in ReviewMode
+ assert self.instruct_content, 'review only support with `schema != "raw"`'
+
+ if strgy == "simple":
+ review_comments = await self.simple_review(review_mode)
+ elif strgy == "complex":
+ # review each child node one-by-one
+ review_comments = {}
+ for _, child in self.children.items():
+ child_review_comment = await child.simple_review(review_mode)
+ review_comments.update(child_review_comment)
+
+ return review_comments
+
+ async def human_revise(self) -> dict[str, str]:
+ review_contents = HumanInteraction().interact_with_instruct_content(
+ instruct_content=self.instruct_content, mapping=self.get_mapping(mode="auto"), interact_type="revise"
+ )
+ # re-fill the ActionNode
+ self.update_instruct_content(review_contents)
+ return review_contents
+
+ def _makeup_nodes_output_with_comment(self, review_comments: dict[str, str]) -> dict[str, str]:
+ instruct_content_dict = self.instruct_content.model_dump()
+ nodes_output = {}
+ for key, value in instruct_content_dict.items():
+ if key in review_comments:
+ nodes_output[key] = {"value": value, "comment": review_comments[key]}
+ return nodes_output
+
+ async def auto_revise(
+ self, revise_mode: ReviseMode = ReviseMode.AUTO, template: str = REVISE_TEMPLATE
+ ) -> dict[str, str]:
+ """revise the value of incorrect keys"""
+ # generate review comments
+ if revise_mode == ReviseMode.AUTO:
+ review_comments: dict = await self.auto_review()
+ elif revise_mode == ReviseMode.HUMAN_REVIEW:
+ review_comments: dict = await self.human_review()
+
+ include_keys = list(review_comments.keys())
+
+ # generate revise content, two-steps
+ # step1, find the needed revise keys from review comments to makeup prompt template
+ nodes_output = self._makeup_nodes_output_with_comment(review_comments)
+ keys = self.keys()
+ exclude_keys = list(set(keys).difference(include_keys))
+ example = self.compile_example(schema="json", mode="auto", tag=TAG, exclude=exclude_keys)
+ instruction = self.compile_instruction(schema="markdown", mode="auto", exclude=exclude_keys)
+
+ prompt = template.format(
+ nodes_output=json.dumps(nodes_output, ensure_ascii=False),
+ example=example,
+ instruction=instruction,
+ constraint=FORMAT_CONSTRAINT,
+ prompt_schema="json",
+ )
+
+ # step2, use `_aask_v1` to get revise structure result
+ output_mapping = self.get_mapping(mode="auto", exclude=exclude_keys)
+ output_class_name = f"{self.key}_AN_REVISE"
+ content, scontent = await self._aask_v1(
+ prompt=prompt, output_class_name=output_class_name, output_data_mapping=output_mapping, schema="json"
+ )
+
+ # re-fill the ActionNode
+ sc_dict = scontent.model_dump()
+ self.update_instruct_content(sc_dict)
+ return sc_dict
+
+ async def simple_revise(self, revise_mode: ReviseMode = ReviseMode.AUTO) -> dict[str, str]:
+ if revise_mode == ReviseMode.HUMAN:
+ revise_contents = await self.human_revise()
+ else:
+ revise_contents = await self.auto_revise(revise_mode)
+
+ return revise_contents
+
+ async def revise(self, strgy: str = "simple", revise_mode: ReviseMode = ReviseMode.AUTO) -> dict[str, str]:
+ """revise the content of ActionNode and update the instruct_content
+
+ :param strgy: simple/complex
+ - simple: run only once
+ - complex: run each node
+ """
+ if not hasattr(self, "llm"):
+ raise RuntimeError("use `revise` after `fill`")
+ assert revise_mode in ReviseMode
+ assert self.instruct_content, 'revise only support with `schema != "raw"`'
+
+ if strgy == "simple":
+ revise_contents = await self.simple_revise(revise_mode)
+ elif strgy == "complex":
+ # revise each child node one-by-one
+ revise_contents = {}
+ for _, child in self.children.items():
+ child_revise_content = await child.simple_revise(revise_mode)
+ revise_contents.update(child_revise_content)
+ self.update_instruct_content(revise_contents)
+
+ return revise_contents
+
+ @classmethod
+ def from_pydantic(cls, model: Type[BaseModel], key: str = None):
+ """
+ Creates an ActionNode tree from a Pydantic model.
+
+ Args:
+ model (Type[BaseModel]): The Pydantic model to convert.
+
+ Returns:
+ ActionNode: The root node of the created ActionNode tree.
+ """
+ key = key or model.__name__
+ root_node = cls(key=model.__name__, expected_type=Type[model], instruction="", example="")
+
+ for field_name, field_model in model.model_fields.items():
+ # Extracting field details
+ expected_type = field_model.annotation
+ instruction = field_model.description or ""
+ example = field_model.default
+
+ # Check if the field is a Pydantic model itself.
+ # Use isinstance to avoid typing.List, typing.Dict, etc. (they are instances of type, not subclasses)
+ if isinstance(expected_type, type) and issubclass(expected_type, BaseModel):
+ # Recursively process the nested model
+ child_node = cls.from_pydantic(expected_type, key=field_name)
+ else:
+ child_node = cls(key=field_name, expected_type=expected_type, instruction=instruction, example=example)
+
+ root_node.add_child(child_node)
+
+ return root_node
+
+
+class ToolUse(BaseModel):
+ tool_name: str = Field(default="a", description="tool name", examples=[])
+
+
+class Task(BaseModel):
+ task_id: int = Field(default="1", description="task id", examples=[1, 2, 3])
+ name: str = Field(default="Get data from ...", description="task name", examples=[])
+ dependent_task_ids: List[int] = Field(default=[], description="dependent task ids", examples=[1, 2, 3])
+ tool: ToolUse = Field(default=ToolUse(), description="tool use", examples=[])
+
+
+class Tasks(BaseModel):
+ tasks: List[Task] = Field(default=[], description="tasks", examples=[])
+
+
+if __name__ == "__main__":
+ node = ActionNode.from_pydantic(Tasks)
+ print("Tasks")
+ print(Tasks.model_json_schema())
+ print("Task")
+ print(Task.model_json_schema())
+ print(node)
+ prompt = node.compile(context="")
+ node.create_children_class()
+ print(prompt)
diff --git a/metagpt/actions/action_outcls_registry.py b/metagpt/actions/action_outcls_registry.py
new file mode 100644
index 000000000..6baa4cea9
--- /dev/null
+++ b/metagpt/actions/action_outcls_registry.py
@@ -0,0 +1,42 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# @Desc : registry to store Dynamic Model from ActionNode.create_model_class to keep it as same Class
+# with same class name and mapping
+
+from functools import wraps
+
+action_outcls_registry = dict()
+
+
+def register_action_outcls(func):
+ """
+ Due to `create_model` return different Class even they have same class name and mapping.
+ In order to do a comparison, use outcls_id to identify same Class with same class name and field definition
+ """
+
+ @wraps(func)
+ def decorater(*args, **kwargs):
+ """
+ arr example
+ [, 'test', {'field': (str, Ellipsis)}]
+ """
+ arr = list(args) + list(kwargs.values())
+ """
+ outcls_id example
+ "_test_{'field': (str, Ellipsis)}"
+ """
+ for idx, item in enumerate(arr):
+ if isinstance(item, dict):
+ arr[idx] = dict(sorted(item.items()))
+ outcls_id = "_".join([str(i) for i in arr])
+ # eliminate typing influence
+ outcls_id = outcls_id.replace("typing.List", "list").replace("typing.Dict", "dict")
+
+ if outcls_id in action_outcls_registry:
+ return action_outcls_registry[outcls_id]
+
+ out_cls = func(*args, **kwargs)
+ action_outcls_registry[outcls_id] = out_cls
+ return out_cls
+
+ return decorater
diff --git a/metagpt/actions/debug_error.py b/metagpt/actions/debug_error.py
index 34f784072..5ed31bed8 100644
--- a/metagpt/actions/debug_error.py
+++ b/metagpt/actions/debug_error.py
@@ -13,12 +13,9 @@ import re
from pydantic import Field
from metagpt.actions.action import Action
-from metagpt.config import CONFIG
-from metagpt.const import TEST_CODES_FILE_REPO, TEST_OUTPUTS_FILE_REPO
from metagpt.logs import logger
from metagpt.schema import RunCodeContext, RunCodeResult
from metagpt.utils.common import CodeParser
-from metagpt.utils.file_repository import FileRepository
PROMPT_TEMPLATE = """
NOTICE
@@ -49,13 +46,10 @@ Now you should start rewriting the code:
class DebugError(Action):
- name: str = "DebugError"
- context: RunCodeContext = Field(default_factory=RunCodeContext)
+ i_context: RunCodeContext = Field(default_factory=RunCodeContext)
async def run(self, *args, **kwargs) -> str:
- output_doc = await FileRepository.get_file(
- filename=self.context.output_filename, relative_path=TEST_OUTPUTS_FILE_REPO
- )
+ output_doc = await self.repo.test_outputs.get(filename=self.i_context.output_filename)
if not output_doc:
return ""
output_detail = RunCodeResult.loads(output_doc.content)
@@ -64,15 +58,13 @@ class DebugError(Action):
if matches:
return ""
- logger.info(f"Debug and rewrite {self.context.test_filename}")
- code_doc = await FileRepository.get_file(
- filename=self.context.code_filename, relative_path=CONFIG.src_workspace
+ logger.info(f"Debug and rewrite {self.i_context.test_filename}")
+ code_doc = await self.repo.with_src_path(self.context.src_workspace).srcs.get(
+ filename=self.i_context.code_filename
)
if not code_doc:
return ""
- test_doc = await FileRepository.get_file(
- filename=self.context.test_filename, relative_path=TEST_CODES_FILE_REPO
- )
+ test_doc = await self.repo.tests.get(filename=self.i_context.test_filename)
if not test_doc:
return ""
prompt = PROMPT_TEMPLATE.format(code=code_doc.content, test_code=test_doc.content, logs=output_detail.stderr)
diff --git a/metagpt/actions/design_api.py b/metagpt/actions/design_api.py
index 2574550e4..cb6013538 100644
--- a/metagpt/actions/design_api.py
+++ b/metagpt/actions/design_api.py
@@ -14,18 +14,17 @@ from pathlib import Path
from typing import Optional
from metagpt.actions import Action, ActionOutput
-from metagpt.actions.design_api_an import DESIGN_API_NODE
-from metagpt.config import CONFIG
-from metagpt.const import (
- DATA_API_DESIGN_FILE_REPO,
- PRDS_FILE_REPO,
- SEQ_FLOW_FILE_REPO,
- SYSTEM_DESIGN_FILE_REPO,
- SYSTEM_DESIGN_PDF_FILE_REPO,
+from metagpt.actions.design_api_an import (
+ DATA_STRUCTURES_AND_INTERFACES,
+ DESIGN_API_NODE,
+ PROGRAM_CALL_FLOW,
+ REFINED_DATA_STRUCTURES_AND_INTERFACES,
+ REFINED_DESIGN_NODE,
+ REFINED_PROGRAM_CALL_FLOW,
)
+from metagpt.const import DATA_API_DESIGN_FILE_REPO, SEQ_FLOW_FILE_REPO
from metagpt.logs import logger
from metagpt.schema import Document, Documents, Message
-from metagpt.utils.file_repository import FileRepository
from metagpt.utils.mermaid import mermaid_to_file
NEW_REQ_TEMPLATE = """
@@ -39,36 +38,30 @@ NEW_REQ_TEMPLATE = """
class WriteDesign(Action):
name: str = ""
- context: Optional[str] = None
+ i_context: Optional[str] = None
desc: str = (
"Based on the PRD, think about the system design, and design the corresponding APIs, "
"data structures, library tables, processes, and paths. Please provide your design, feedback "
"clearly and in detail."
)
- async def run(self, with_messages: Message, schema: str = CONFIG.prompt_schema):
- # Use `git status` to identify which PRD documents have been modified in the `docs/prds` directory.
- prds_file_repo = CONFIG.git_repo.new_file_repository(PRDS_FILE_REPO)
- changed_prds = prds_file_repo.changed_files
+ async def run(self, with_messages: Message, schema: str = None):
+ # Use `git status` to identify which PRD documents have been modified in the `docs/prd` directory.
+ changed_prds = self.repo.docs.prd.changed_files
# Use `git status` to identify which design documents in the `docs/system_designs` directory have undergone
# changes.
- system_design_file_repo = CONFIG.git_repo.new_file_repository(SYSTEM_DESIGN_FILE_REPO)
- changed_system_designs = system_design_file_repo.changed_files
+ changed_system_designs = self.repo.docs.system_design.changed_files
# For those PRDs and design documents that have undergone changes, regenerate the design content.
changed_files = Documents()
for filename in changed_prds.keys():
- doc = await self._update_system_design(
- filename=filename, prds_file_repo=prds_file_repo, system_design_file_repo=system_design_file_repo
- )
+ doc = await self._update_system_design(filename=filename)
changed_files.docs[filename] = doc
for filename in changed_system_designs.keys():
if filename in changed_files.docs:
continue
- doc = await self._update_system_design(
- filename=filename, prds_file_repo=prds_file_repo, system_design_file_repo=system_design_file_repo
- )
+ doc = await self._update_system_design(filename=filename)
changed_files.docs[filename] = doc
if not changed_files.docs:
logger.info("Nothing has changed.")
@@ -76,61 +69,52 @@ class WriteDesign(Action):
# leaving room for global optimization in subsequent steps.
return ActionOutput(content=changed_files.model_dump_json(), instruct_content=changed_files)
- async def _new_system_design(self, context, schema=CONFIG.prompt_schema):
- node = await DESIGN_API_NODE.fill(context=context, llm=self.llm, schema=schema)
+ async def _new_system_design(self, context):
+ node = await DESIGN_API_NODE.fill(context=context, llm=self.llm)
return node
- async def _merge(self, prd_doc, system_design_doc, schema=CONFIG.prompt_schema):
+ async def _merge(self, prd_doc, system_design_doc):
context = NEW_REQ_TEMPLATE.format(old_design=system_design_doc.content, context=prd_doc.content)
- node = await DESIGN_API_NODE.fill(context=context, llm=self.llm, schema=schema)
+ node = await REFINED_DESIGN_NODE.fill(context=context, llm=self.llm)
system_design_doc.content = node.instruct_content.model_dump_json()
return system_design_doc
- async def _update_system_design(self, filename, prds_file_repo, system_design_file_repo) -> Document:
- prd = await prds_file_repo.get(filename)
- old_system_design_doc = await system_design_file_repo.get(filename)
+ async def _update_system_design(self, filename) -> Document:
+ prd = await self.repo.docs.prd.get(filename)
+ old_system_design_doc = await self.repo.docs.system_design.get(filename)
if not old_system_design_doc:
system_design = await self._new_system_design(context=prd.content)
- doc = Document(
- root_path=SYSTEM_DESIGN_FILE_REPO,
+ doc = await self.repo.docs.system_design.save(
filename=filename,
content=system_design.instruct_content.model_dump_json(),
+ dependencies={prd.root_relative_path},
)
else:
doc = await self._merge(prd_doc=prd, system_design_doc=old_system_design_doc)
- await system_design_file_repo.save(
- filename=filename, content=doc.content, dependencies={prd.root_relative_path}
- )
+ await self.repo.docs.system_design.save_doc(doc=doc, dependencies={prd.root_relative_path})
await self._save_data_api_design(doc)
await self._save_seq_flow(doc)
- await self._save_pdf(doc)
+ await self.repo.resources.system_design.save_pdf(doc=doc)
return doc
- @staticmethod
- async def _save_data_api_design(design_doc):
+ async def _save_data_api_design(self, design_doc):
m = json.loads(design_doc.content)
- data_api_design = m.get("Data structures and interfaces")
+ data_api_design = m.get(DATA_STRUCTURES_AND_INTERFACES.key) or m.get(REFINED_DATA_STRUCTURES_AND_INTERFACES.key)
if not data_api_design:
return
- pathname = CONFIG.git_repo.workdir / DATA_API_DESIGN_FILE_REPO / Path(design_doc.filename).with_suffix("")
- await WriteDesign._save_mermaid_file(data_api_design, pathname)
+ pathname = self.repo.workdir / DATA_API_DESIGN_FILE_REPO / Path(design_doc.filename).with_suffix("")
+ await self._save_mermaid_file(data_api_design, pathname)
logger.info(f"Save class view to {str(pathname)}")
- @staticmethod
- async def _save_seq_flow(design_doc):
+ async def _save_seq_flow(self, design_doc):
m = json.loads(design_doc.content)
- seq_flow = m.get("Program call flow")
+ seq_flow = m.get(PROGRAM_CALL_FLOW.key) or m.get(REFINED_PROGRAM_CALL_FLOW.key)
if not seq_flow:
return
- pathname = CONFIG.git_repo.workdir / Path(SEQ_FLOW_FILE_REPO) / Path(design_doc.filename).with_suffix("")
- await WriteDesign._save_mermaid_file(seq_flow, pathname)
+ pathname = self.repo.workdir / Path(SEQ_FLOW_FILE_REPO) / Path(design_doc.filename).with_suffix("")
+ await self._save_mermaid_file(seq_flow, pathname)
logger.info(f"Saving sequence flow to {str(pathname)}")
- @staticmethod
- async def _save_pdf(design_doc):
- await FileRepository.save_as(doc=design_doc, with_suffix=".md", relative_path=SYSTEM_DESIGN_PDF_FILE_REPO)
-
- @staticmethod
- async def _save_mermaid_file(data: str, pathname: Path):
+ async def _save_mermaid_file(self, data: str, pathname: Path):
pathname.parent.mkdir(parents=True, exist_ok=True)
- await mermaid_to_file(data, pathname)
+ await mermaid_to_file(self.config.mermaid_engine, data, pathname)
diff --git a/metagpt/actions/design_api_an.py b/metagpt/actions/design_api_an.py
index 3737203cf..35b50ef8f 100644
--- a/metagpt/actions/design_api_an.py
+++ b/metagpt/actions/design_api_an.py
@@ -8,6 +8,7 @@
from typing import List
from metagpt.actions.action_node import ActionNode
+from metagpt.logs import logger
from metagpt.utils.mermaid import MMC1, MMC2
IMPLEMENTATION_APPROACH = ActionNode(
@@ -17,6 +18,15 @@ IMPLEMENTATION_APPROACH = ActionNode(
example="We will ...",
)
+REFINED_IMPLEMENTATION_APPROACH = ActionNode(
+ key="Refined Implementation Approach",
+ expected_type=str,
+ instruction="Update and extend the original implementation approach to reflect the evolving challenges and "
+ "requirements due to incremental development. Outline the steps involved in the implementation process with the "
+ "detailed strategies.",
+ example="We will refine ...",
+)
+
PROJECT_NAME = ActionNode(
key="Project name", expected_type=str, instruction="The project name with underline", example="game_2048"
)
@@ -28,6 +38,14 @@ FILE_LIST = ActionNode(
example=["main.py", "game.py"],
)
+REFINED_FILE_LIST = ActionNode(
+ key="Refined File list",
+ expected_type=List[str],
+ instruction="Update and expand the original file list including only relative paths. Up to 2 files can be added."
+ "Ensure that the refined file list reflects the evolving structure of the project.",
+ example=["main.py", "game.py", "new_feature.py"],
+)
+
DATA_STRUCTURES_AND_INTERFACES = ActionNode(
key="Data structures and interfaces",
expected_type=str,
@@ -37,6 +55,16 @@ DATA_STRUCTURES_AND_INTERFACES = ActionNode(
example=MMC1,
)
+REFINED_DATA_STRUCTURES_AND_INTERFACES = ActionNode(
+ key="Refined Data structures and interfaces",
+ expected_type=str,
+ instruction="Update and extend the existing mermaid classDiagram code syntax to incorporate new classes, "
+ "methods (including __init__), and functions with precise type annotations. Delineate additional "
+ "relationships between classes, ensuring clarity and adherence to PEP8 standards."
+ "Retain content that is not related to incremental development but important for consistency and clarity.",
+ example=MMC1,
+)
+
PROGRAM_CALL_FLOW = ActionNode(
key="Program call flow",
expected_type=str,
@@ -45,6 +73,16 @@ PROGRAM_CALL_FLOW = ActionNode(
example=MMC2,
)
+REFINED_PROGRAM_CALL_FLOW = ActionNode(
+ key="Refined Program call flow",
+ expected_type=str,
+ instruction="Extend the existing sequenceDiagram code syntax with detailed information, accurately covering the"
+ "CRUD and initialization of each object. Ensure correct syntax usage and reflect the incremental changes introduced"
+ "in the classes and API defined above. "
+ "Retain content that is not related to incremental development but important for consistency and clarity.",
+ example=MMC2,
+)
+
ANYTHING_UNCLEAR = ActionNode(
key="Anything UNCLEAR",
expected_type=str,
@@ -61,4 +99,24 @@ NODES = [
ANYTHING_UNCLEAR,
]
+REFINED_NODES = [
+ REFINED_IMPLEMENTATION_APPROACH,
+ REFINED_FILE_LIST,
+ REFINED_DATA_STRUCTURES_AND_INTERFACES,
+ REFINED_PROGRAM_CALL_FLOW,
+ ANYTHING_UNCLEAR,
+]
+
DESIGN_API_NODE = ActionNode.from_children("DesignAPI", NODES)
+REFINED_DESIGN_NODE = ActionNode.from_children("RefinedDesignAPI", REFINED_NODES)
+
+
+def main():
+ prompt = DESIGN_API_NODE.compile(context="")
+ logger.info(prompt)
+ prompt = REFINED_DESIGN_NODE.compile(context="")
+ logger.info(prompt)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/metagpt/actions/design_api_review.py b/metagpt/actions/design_api_review.py
index fb1b92d85..ccd01a4c3 100644
--- a/metagpt/actions/design_api_review.py
+++ b/metagpt/actions/design_api_review.py
@@ -13,7 +13,7 @@ from metagpt.actions.action import Action
class DesignReview(Action):
name: str = "DesignReview"
- context: Optional[str] = None
+ i_context: Optional[str] = None
async def run(self, prd, api_design):
prompt = (
diff --git a/metagpt/actions/execute_code.py b/metagpt/actions/execute_code.py
index 6591f479f..6a4a9abb8 100644
--- a/metagpt/actions/execute_code.py
+++ b/metagpt/actions/execute_code.py
@@ -123,7 +123,10 @@ class ExecutePyCode(ExecuteCode, Action):
return parsed_output
for i, output in enumerate(outputs):
- if output["output_type"] == "stream":
+ if output["output_type"] == "stream" and not any(
+ tag in output["text"]
+ for tag in ["| INFO | metagpt", "| ERROR | metagpt", "| WARNING | metagpt"]
+ ):
parsed_output += output["text"]
elif output["output_type"] == "display_data":
if "image/png" in output["data"]:
diff --git a/metagpt/actions/execute_task.py b/metagpt/actions/execute_task.py
index 4ae4ee17b..1cc3bd699 100644
--- a/metagpt/actions/execute_task.py
+++ b/metagpt/actions/execute_task.py
@@ -13,7 +13,7 @@ from metagpt.schema import Message
class ExecuteTask(Action):
name: str = "ExecuteTask"
- context: list[Message] = []
+ i_context: list[Message] = []
async def run(self, *args, **kwargs):
pass
diff --git a/metagpt/actions/generate_questions.py b/metagpt/actions/generate_questions.py
index 8573708f2..c96a37649 100644
--- a/metagpt/actions/generate_questions.py
+++ b/metagpt/actions/generate_questions.py
@@ -1,8 +1,6 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
-@Time : 2023/9/12 17:45
-@Author : fisherdeng
@File : generate_questions.py
"""
from metagpt.actions import Action
@@ -23,5 +21,5 @@ class GenerateQuestions(Action):
name: str = "GenerateQuestions"
- async def run(self, context):
+ async def run(self, context) -> ActionNode:
return await QUESTIONS.fill(context=context, llm=self.llm)
diff --git a/metagpt/actions/invoice_ocr.py b/metagpt/actions/invoice_ocr.py
index 36570097a..7cf71a8ff 100644
--- a/metagpt/actions/invoice_ocr.py
+++ b/metagpt/actions/invoice_ocr.py
@@ -16,17 +16,14 @@ from typing import Optional
import pandas as pd
from paddleocr import PaddleOCR
-from pydantic import Field
from metagpt.actions import Action
from metagpt.const import INVOICE_OCR_TABLE_PATH
-from metagpt.llm import LLM
from metagpt.logs import logger
from metagpt.prompts.invoice_ocr import (
EXTRACT_OCR_MAIN_INFO_PROMPT,
REPLY_OCR_QUESTION_PROMPT,
)
-from metagpt.provider.base_llm import BaseLLM
from metagpt.utils.common import OutputParser
from metagpt.utils.file import File
@@ -41,7 +38,7 @@ class InvoiceOCR(Action):
"""
name: str = "InvoiceOCR"
- context: Optional[str] = None
+ i_context: Optional[str] = None
@staticmethod
async def _check_file_type(file_path: Path) -> str:
@@ -132,8 +129,7 @@ class GenerateTable(Action):
"""
name: str = "GenerateTable"
- context: Optional[str] = None
- llm: BaseLLM = Field(default_factory=LLM)
+ i_context: Optional[str] = None
language: str = "ch"
async def run(self, ocr_results: list, filename: str, *args, **kwargs) -> dict[str, str]:
@@ -176,9 +172,6 @@ class ReplyQuestion(Action):
"""
- name: str = "ReplyQuestion"
- context: Optional[str] = None
- llm: BaseLLM = Field(default_factory=LLM)
language: str = "ch"
async def run(self, query: str, ocr_result: list, *args, **kwargs) -> str:
diff --git a/metagpt/actions/ml_da_action.py b/metagpt/actions/ml_action.py
similarity index 52%
rename from metagpt/actions/ml_da_action.py
rename to metagpt/actions/ml_action.py
index d4e77773f..a61233e5a 100644
--- a/metagpt/actions/ml_da_action.py
+++ b/metagpt/actions/ml_action.py
@@ -1,28 +1,64 @@
import json
+from typing import List, Tuple
from metagpt.actions import Action
-from metagpt.prompts.ml_engineer import PRINT_DATA_COLUMNS, UPDATE_DATA_COLUMNS
-from metagpt.schema import Plan
+from metagpt.actions.write_analysis_code import WriteCodeWithTools
+from metagpt.prompts.ml_action import (
+ GENERATE_CODE_PROMPT,
+ ML_TOOL_USAGE_PROMPT,
+ PRINT_DATA_COLUMNS,
+ UPDATE_DATA_COLUMNS,
+)
+from metagpt.prompts.write_analysis_code import CODE_GENERATOR_WITH_TOOLS
+from metagpt.schema import Message, Plan
from metagpt.utils.common import CodeParser, create_func_config, remove_comments
-class SummarizeAnalysis(Action):
- PROMPT_TEMPLATE: str = """
- # Context
- {context}
- # Summary
- Output a 30-word summary on analysis tool and modeling algorithms you have used, and the corresponding result. Make sure to announce the complete path to your test prediction file. Your summary:
- """
+class WriteCodeWithToolsML(WriteCodeWithTools):
+ async def run(
+ self,
+ context: List[Message],
+ plan: Plan = None,
+ column_info: str = "",
+ **kwargs,
+ ) -> Tuple[List[Message], str]:
+ # prepare tool schemas and tool-type-specific instruction
+ tool_schemas, tool_type_usage_prompt = await self._prepare_tools(plan=plan)
- async def run(self, conmpleted_plan: Plan) -> str:
- tasks = json.dumps(
- [task.dict() for task in conmpleted_plan.tasks],
- indent=4,
- ensure_ascii=False,
- ) # all tasks finished, return all task outputs
- prompt = self.PROMPT_TEMPLATE.format(context=tasks)
- summary = await self._aask(prompt)
- return summary
+ # ML-specific variables to be used in prompt
+ code_steps = plan.current_task.code_steps
+ finished_tasks = plan.get_finished_tasks()
+ code_context = [remove_comments(task.code) for task in finished_tasks]
+ code_context = "\n\n".join(code_context)
+
+ # prepare prompt depending on tool availability & LLM call
+ if tool_schemas:
+ prompt = ML_TOOL_USAGE_PROMPT.format(
+ user_requirement=plan.goal,
+ history_code=code_context,
+ current_task=plan.current_task.instruction,
+ column_info=column_info,
+ tool_type_usage_prompt=tool_type_usage_prompt,
+ code_steps=code_steps,
+ tool_schemas=tool_schemas,
+ )
+
+ else:
+ prompt = GENERATE_CODE_PROMPT.format(
+ user_requirement=plan.goal,
+ history_code=code_context,
+ current_task=plan.current_task.instruction,
+ column_info=column_info,
+ tool_type_usage_prompt=tool_type_usage_prompt,
+ code_steps=code_steps,
+ )
+ tool_config = create_func_config(CODE_GENERATOR_WITH_TOOLS)
+ rsp = await self.llm.aask_code(prompt, **tool_config)
+
+ # Extra output to be used for potential debugging
+ context = [Message(content=prompt, role="user")]
+
+ return context, rsp
class Reflect(Action):
diff --git a/metagpt/actions/prepare_documents.py b/metagpt/actions/prepare_documents.py
index 5c5798d95..ab069dc11 100644
--- a/metagpt/actions/prepare_documents.py
+++ b/metagpt/actions/prepare_documents.py
@@ -12,39 +12,41 @@ from pathlib import Path
from typing import Optional
from metagpt.actions import Action, ActionOutput
-from metagpt.config import CONFIG
-from metagpt.const import DOCS_FILE_REPO, REQUIREMENT_FILENAME
-from metagpt.schema import Document
+from metagpt.const import REQUIREMENT_FILENAME
from metagpt.utils.file_repository import FileRepository
from metagpt.utils.git_repository import GitRepository
+from metagpt.utils.project_repo import ProjectRepo
class PrepareDocuments(Action):
"""PrepareDocuments Action: initialize project folder and add new requirements to docs/requirements.txt."""
name: str = "PrepareDocuments"
- context: Optional[str] = None
+ i_context: Optional[str] = None
+
+ @property
+ def config(self):
+ return self.context.config
def _init_repo(self):
"""Initialize the Git environment."""
- if not CONFIG.project_path:
- name = CONFIG.project_name or FileRepository.new_filename()
- path = Path(CONFIG.workspace_path) / name
+ if not self.config.project_path:
+ name = self.config.project_name or FileRepository.new_filename()
+ path = Path(self.config.workspace.path) / name
else:
- path = Path(CONFIG.project_path)
- if path.exists() and not CONFIG.inc:
+ path = Path(self.config.project_path)
+ if path.exists() and not self.config.inc:
shutil.rmtree(path)
- CONFIG.project_path = path
- CONFIG.git_repo = GitRepository(local_path=path, auto_init=True)
+ self.config.project_path = path
+ self.context.git_repo = GitRepository(local_path=path, auto_init=True)
+ self.context.repo = ProjectRepo(self.context.git_repo)
async def run(self, with_messages, **kwargs):
"""Create and initialize the workspace folder, initialize the Git environment."""
self._init_repo()
# Write the newly added requirements from the main parameter idea to `docs/requirement.txt`.
- doc = Document(root_path=DOCS_FILE_REPO, filename=REQUIREMENT_FILENAME, content=with_messages[0].content)
- await FileRepository.save_file(filename=REQUIREMENT_FILENAME, content=doc.content, relative_path=DOCS_FILE_REPO)
-
+ doc = await self.repo.docs.save(filename=REQUIREMENT_FILENAME, content=with_messages[0].content)
# Send a Message notification to the WritePRD action, instructing it to process requirements using
- # `docs/requirement.txt` and `docs/prds/`.
+ # `docs/requirement.txt` and `docs/prd/`.
return ActionOutput(content=doc.content, instruct_content=doc)
diff --git a/metagpt/actions/project_management.py b/metagpt/actions/project_management.py
index e40c2034b..67a614d6f 100644
--- a/metagpt/actions/project_management.py
+++ b/metagpt/actions/project_management.py
@@ -13,23 +13,16 @@
import json
from typing import Optional
-from metagpt.actions import ActionOutput
from metagpt.actions.action import Action
-from metagpt.actions.project_management_an import PM_NODE
-from metagpt.config import CONFIG
-from metagpt.const import (
- PACKAGE_REQUIREMENTS_FILENAME,
- SYSTEM_DESIGN_FILE_REPO,
- TASK_FILE_REPO,
- TASK_PDF_FILE_REPO,
-)
+from metagpt.actions.action_output import ActionOutput
+from metagpt.actions.project_management_an import PM_NODE, REFINED_PM_NODE
+from metagpt.const import PACKAGE_REQUIREMENTS_FILENAME
from metagpt.logs import logger
from metagpt.schema import Document, Documents
-from metagpt.utils.file_repository import FileRepository
NEW_REQ_TEMPLATE = """
### Legacy Content
-{old_tasks}
+{old_task}
### New Requirements
{context}
@@ -38,30 +31,23 @@ NEW_REQ_TEMPLATE = """
class WriteTasks(Action):
name: str = "CreateTasks"
- context: Optional[str] = None
+ i_context: Optional[str] = None
- async def run(self, with_messages, schema=CONFIG.prompt_schema):
- system_design_file_repo = CONFIG.git_repo.new_file_repository(SYSTEM_DESIGN_FILE_REPO)
- changed_system_designs = system_design_file_repo.changed_files
-
- tasks_file_repo = CONFIG.git_repo.new_file_repository(TASK_FILE_REPO)
- changed_tasks = tasks_file_repo.changed_files
+ async def run(self, with_messages):
+ changed_system_designs = self.repo.docs.system_design.changed_files
+ changed_tasks = self.repo.docs.task.changed_files
change_files = Documents()
# Rewrite the system designs that have undergone changes based on the git head diff under
# `docs/system_designs/`.
for filename in changed_system_designs:
- task_doc = await self._update_tasks(
- filename=filename, system_design_file_repo=system_design_file_repo, tasks_file_repo=tasks_file_repo
- )
+ task_doc = await self._update_tasks(filename=filename)
change_files.docs[filename] = task_doc
# Rewrite the task files that have undergone changes based on the git head diff under `docs/tasks/`.
for filename in changed_tasks:
if filename in change_files.docs:
continue
- task_doc = await self._update_tasks(
- filename=filename, system_design_file_repo=system_design_file_repo, tasks_file_repo=tasks_file_repo
- )
+ task_doc = await self._update_tasks(filename=filename)
change_files.docs[filename] = task_doc
if not change_files.docs:
@@ -70,39 +56,36 @@ class WriteTasks(Action):
# global optimization in subsequent steps.
return ActionOutput(content=change_files.model_dump_json(), instruct_content=change_files)
- async def _update_tasks(self, filename, system_design_file_repo, tasks_file_repo):
- system_design_doc = await system_design_file_repo.get(filename)
- task_doc = await tasks_file_repo.get(filename)
+ async def _update_tasks(self, filename):
+ system_design_doc = await self.repo.docs.system_design.get(filename)
+ task_doc = await self.repo.docs.task.get(filename)
if task_doc:
task_doc = await self._merge(system_design_doc=system_design_doc, task_doc=task_doc)
+ await self.repo.docs.task.save_doc(doc=task_doc, dependencies={system_design_doc.root_relative_path})
else:
rsp = await self._run_new_tasks(context=system_design_doc.content)
- task_doc = Document(
- root_path=TASK_FILE_REPO, filename=filename, content=rsp.instruct_content.model_dump_json()
+ task_doc = await self.repo.docs.task.save(
+ filename=filename,
+ content=rsp.instruct_content.model_dump_json(),
+ dependencies={system_design_doc.root_relative_path},
)
- await tasks_file_repo.save(
- filename=filename, content=task_doc.content, dependencies={system_design_doc.root_relative_path}
- )
await self._update_requirements(task_doc)
- await self._save_pdf(task_doc=task_doc)
return task_doc
- async def _run_new_tasks(self, context, schema=CONFIG.prompt_schema):
- node = await PM_NODE.fill(context, self.llm, schema)
+ async def _run_new_tasks(self, context):
+ node = await PM_NODE.fill(context, self.llm, schema=self.prompt_schema)
return node
- async def _merge(self, system_design_doc, task_doc, schema=CONFIG.prompt_schema) -> Document:
- context = NEW_REQ_TEMPLATE.format(context=system_design_doc.content, old_tasks=task_doc.content)
- node = await PM_NODE.fill(context, self.llm, schema)
+ async def _merge(self, system_design_doc, task_doc) -> Document:
+ context = NEW_REQ_TEMPLATE.format(context=system_design_doc.content, old_task=task_doc.content)
+ node = await REFINED_PM_NODE.fill(context, self.llm, schema=self.prompt_schema)
task_doc.content = node.instruct_content.model_dump_json()
return task_doc
- @staticmethod
- async def _update_requirements(doc):
+ async def _update_requirements(self, doc):
m = json.loads(doc.content)
- packages = set(m.get("Required Python third-party packages", set()))
- file_repo = CONFIG.git_repo.new_file_repository()
- requirement_doc = await file_repo.get(filename=PACKAGE_REQUIREMENTS_FILENAME)
+ packages = set(m.get("Required Python packages", set()))
+ requirement_doc = await self.repo.get(filename=PACKAGE_REQUIREMENTS_FILENAME)
if not requirement_doc:
requirement_doc = Document(filename=PACKAGE_REQUIREMENTS_FILENAME, root_path=".", content="")
lines = requirement_doc.content.splitlines()
@@ -110,8 +93,4 @@ class WriteTasks(Action):
if pkg == "":
continue
packages.add(pkg)
- await file_repo.save(PACKAGE_REQUIREMENTS_FILENAME, content="\n".join(packages))
-
- @staticmethod
- async def _save_pdf(task_doc):
- await FileRepository.save_as(doc=task_doc, with_suffix=".md", relative_path=TASK_PDF_FILE_REPO)
+ await self.repo.save(filename=PACKAGE_REQUIREMENTS_FILENAME, content="\n".join(packages))
diff --git a/metagpt/actions/project_management_an.py b/metagpt/actions/project_management_an.py
index 215a67202..379a23384 100644
--- a/metagpt/actions/project_management_an.py
+++ b/metagpt/actions/project_management_an.py
@@ -35,6 +35,20 @@ LOGIC_ANALYSIS = ActionNode(
],
)
+REFINED_LOGIC_ANALYSIS = ActionNode(
+ key="Refined Logic Analysis",
+ expected_type=List[List[str]],
+ instruction="Review and refine the logic analysis by merging the Legacy Content and Incremental Content. "
+ "Provide a comprehensive list of files with classes/methods/functions to be implemented or modified incrementally. "
+ "Include dependency analysis, consider potential impacts on existing code, and document necessary imports.",
+ example=[
+ ["game.py", "Contains Game class and ... functions"],
+ ["main.py", "Contains main function, from game import Game"],
+ ["new_feature.py", "Introduces NewFeature class and related functions"],
+ ["utils.py", "Modifies existing utility functions to support incremental changes"],
+ ],
+)
+
TASK_LIST = ActionNode(
key="Task list",
expected_type=List[str],
@@ -42,6 +56,15 @@ TASK_LIST = ActionNode(
example=["game.py", "main.py"],
)
+REFINED_TASK_LIST = ActionNode(
+ key="Refined Task list",
+ expected_type=List[str],
+ instruction="Review and refine the combined task list after the merger of Legacy Content and Incremental Content, "
+ "and consistent with Refined File List. Ensure that tasks are organized in a logical and prioritized order, "
+ "considering dependencies for a streamlined and efficient development process. ",
+ example=["new_feature.py", "utils", "game.py", "main.py"],
+)
+
FULL_API_SPEC = ActionNode(
key="Full API spec",
expected_type=str,
@@ -54,9 +77,19 @@ SHARED_KNOWLEDGE = ActionNode(
key="Shared Knowledge",
expected_type=str,
instruction="Detail any shared knowledge, like common utility functions or configuration variables.",
- example="'game.py' contains functions shared across the project.",
+ example="`game.py` contains functions shared across the project.",
)
+REFINED_SHARED_KNOWLEDGE = ActionNode(
+ key="Refined Shared Knowledge",
+ expected_type=str,
+ instruction="Update and expand shared knowledge to reflect any new elements introduced. This includes common "
+ "utility functions, configuration variables for team collaboration. Retain content that is not related to "
+ "incremental development but important for consistency and clarity.",
+ example="`new_module.py` enhances shared utility functions for improved code reusability and collaboration.",
+)
+
+
ANYTHING_UNCLEAR_PM = ActionNode(
key="Anything UNCLEAR",
expected_type=str,
@@ -74,13 +107,25 @@ NODES = [
ANYTHING_UNCLEAR_PM,
]
+REFINED_NODES = [
+ REQUIRED_PYTHON_PACKAGES,
+ REQUIRED_OTHER_LANGUAGE_PACKAGES,
+ REFINED_LOGIC_ANALYSIS,
+ REFINED_TASK_LIST,
+ FULL_API_SPEC,
+ REFINED_SHARED_KNOWLEDGE,
+ ANYTHING_UNCLEAR_PM,
+]
PM_NODE = ActionNode.from_children("PM_NODE", NODES)
+REFINED_PM_NODE = ActionNode.from_children("REFINED_PM_NODE", REFINED_NODES)
def main():
prompt = PM_NODE.compile(context="")
logger.info(prompt)
+ prompt = REFINED_PM_NODE.compile(context="")
+ logger.info(prompt)
if __name__ == "__main__":
diff --git a/metagpt/actions/rebuild_class_view.py b/metagpt/actions/rebuild_class_view.py
index 5128b9fee..2140ad874 100644
--- a/metagpt/actions/rebuild_class_view.py
+++ b/metagpt/actions/rebuild_class_view.py
@@ -12,7 +12,7 @@ from pathlib import Path
import aiofiles
from metagpt.actions import Action
-from metagpt.config import CONFIG
+from metagpt.config2 import config
from metagpt.const import (
AGGREGATION,
COMPOSITION,
@@ -29,16 +29,16 @@ from metagpt.utils.graph_repository import GraphKeyword, GraphRepository
class RebuildClassView(Action):
- async def run(self, with_messages=None, format=CONFIG.prompt_schema):
- graph_repo_pathname = CONFIG.git_repo.workdir / GRAPH_REPO_FILE_REPO / CONFIG.git_repo.workdir.name
+ async def run(self, with_messages=None, format=config.prompt_schema):
+ graph_repo_pathname = self.context.git_repo.workdir / GRAPH_REPO_FILE_REPO / self.context.git_repo.workdir.name
graph_db = await DiGraphRepository.load_from(str(graph_repo_pathname.with_suffix(".json")))
- repo_parser = RepoParser(base_directory=Path(self.context))
+ repo_parser = RepoParser(base_directory=Path(self.i_context))
# use pylint
- class_views, relationship_views, package_root = await repo_parser.rebuild_class_views(path=Path(self.context))
+ class_views, relationship_views, package_root = await repo_parser.rebuild_class_views(path=Path(self.i_context))
await GraphRepository.update_graph_db_with_class_views(graph_db, class_views)
await GraphRepository.update_graph_db_with_class_relationship_views(graph_db, relationship_views)
# use ast
- direction, diff_path = self._diff_path(path_root=Path(self.context).resolve(), package_root=package_root)
+ direction, diff_path = self._diff_path(path_root=Path(self.i_context).resolve(), package_root=package_root)
symbols = repo_parser.generate_symbols()
for file_info in symbols:
# Align to the same root directory in accordance with `class_views`.
@@ -48,9 +48,9 @@ class RebuildClassView(Action):
await graph_db.save()
async def _create_mermaid_class_views(self, graph_db):
- path = Path(CONFIG.git_repo.workdir) / DATA_API_DESIGN_FILE_REPO
+ path = Path(self.context.git_repo.workdir) / DATA_API_DESIGN_FILE_REPO
path.mkdir(parents=True, exist_ok=True)
- pathname = path / CONFIG.git_repo.workdir.name
+ pathname = path / self.context.git_repo.workdir.name
async with aiofiles.open(str(pathname.with_suffix(".mmd")), mode="w", encoding="utf-8") as writer:
content = "classDiagram\n"
logger.debug(content)
diff --git a/metagpt/actions/rebuild_sequence_view.py b/metagpt/actions/rebuild_sequence_view.py
index 865050c93..777dde8ce 100644
--- a/metagpt/actions/rebuild_sequence_view.py
+++ b/metagpt/actions/rebuild_sequence_view.py
@@ -12,7 +12,7 @@ from pathlib import Path
from typing import List
from metagpt.actions import Action
-from metagpt.config import CONFIG
+from metagpt.config2 import config
from metagpt.const import GRAPH_REPO_FILE_REPO
from metagpt.logs import logger
from metagpt.utils.common import aread, list_files
@@ -21,8 +21,8 @@ from metagpt.utils.graph_repository import GraphKeyword
class RebuildSequenceView(Action):
- async def run(self, with_messages=None, format=CONFIG.prompt_schema):
- graph_repo_pathname = CONFIG.git_repo.workdir / GRAPH_REPO_FILE_REPO / CONFIG.git_repo.workdir.name
+ async def run(self, with_messages=None, format=config.prompt_schema):
+ graph_repo_pathname = self.context.git_repo.workdir / GRAPH_REPO_FILE_REPO / self.context.git_repo.workdir.name
graph_db = await DiGraphRepository.load_from(str(graph_repo_pathname.with_suffix(".json")))
entries = await RebuildSequenceView._search_main_entry(graph_db)
for entry in entries:
@@ -41,7 +41,9 @@ class RebuildSequenceView(Action):
async def _rebuild_sequence_view(self, entry, graph_db):
filename = entry.subject.split(":", 1)[0]
- src_filename = RebuildSequenceView._get_full_filename(root=self.context, pathname=filename)
+ src_filename = RebuildSequenceView._get_full_filename(root=self.i_context, pathname=filename)
+ if not src_filename:
+ return
content = await aread(filename=src_filename, encoding="utf-8")
content = f"```python\n{content}\n```\n\n---\nTranslate the code above into Mermaid Sequence Diagram."
data = await self.llm.aask(
diff --git a/metagpt/actions/research.py b/metagpt/actions/research.py
index 90b08cb6a..2755628c9 100644
--- a/metagpt/actions/research.py
+++ b/metagpt/actions/research.py
@@ -8,10 +8,8 @@ from typing import Callable, Optional, Union
from pydantic import Field, parse_obj_as
from metagpt.actions import Action
-from metagpt.config import CONFIG
-from metagpt.llm import LLM
+from metagpt.config2 import config
from metagpt.logs import logger
-from metagpt.provider.base_llm import BaseLLM
from metagpt.tools.search_engine import SearchEngine
from metagpt.tools.web_browser_engine import WebBrowserEngine, WebBrowserEngineType
from metagpt.utils.common import OutputParser
@@ -81,7 +79,7 @@ class CollectLinks(Action):
"""Action class to collect links from a search engine."""
name: str = "CollectLinks"
- context: Optional[str] = None
+ i_context: Optional[str] = None
desc: str = "Collect links from a search engine."
search_engine: SearchEngine = Field(default_factory=SearchEngine)
@@ -129,8 +127,8 @@ class CollectLinks(Action):
if len(remove) == 0:
break
- model_name = CONFIG.get_model_name(CONFIG.get_default_llm_provider_enum())
- prompt = reduce_message_length(gen_msg(), model_name, system_text, CONFIG.max_tokens_rsp)
+ model_name = config.get_openai_llm().model
+ prompt = reduce_message_length(gen_msg(), model_name, system_text, 4096)
logger.debug(prompt)
queries = await self._aask(prompt, [system_text])
try:
@@ -177,19 +175,16 @@ class WebBrowseAndSummarize(Action):
"""Action class to explore the web and provide summaries of articles and webpages."""
name: str = "WebBrowseAndSummarize"
- context: Optional[str] = None
- llm: BaseLLM = Field(default_factory=LLM)
+ i_context: Optional[str] = None
desc: str = "Explore the web and provide summaries of articles and webpages."
browse_func: Union[Callable[[list[str]], None], None] = None
- web_browser_engine: Optional[WebBrowserEngine] = None
+ web_browser_engine: Optional[WebBrowserEngine] = WebBrowserEngineType.PLAYWRIGHT
def __init__(self, **kwargs):
super().__init__(**kwargs)
- if CONFIG.model_for_researcher_summary:
- self.llm.model = CONFIG.model_for_researcher_summary
self.web_browser_engine = WebBrowserEngine(
- engine=WebBrowserEngineType.CUSTOM if self.browse_func else None,
+ engine=WebBrowserEngineType.CUSTOM if self.browse_func else WebBrowserEngineType.PLAYWRIGHT,
run_func=self.browse_func,
)
@@ -220,9 +215,7 @@ class WebBrowseAndSummarize(Action):
for u, content in zip([url, *urls], contents):
content = content.inner_text
chunk_summaries = []
- for prompt in generate_prompt_chunk(
- content, prompt_template, self.llm.model, system_text, CONFIG.max_tokens_rsp
- ):
+ for prompt in generate_prompt_chunk(content, prompt_template, self.llm.model, system_text, 4096):
logger.debug(prompt)
summary = await self._aask(prompt, [system_text])
if summary == "Not relevant.":
@@ -247,14 +240,8 @@ class WebBrowseAndSummarize(Action):
class ConductResearch(Action):
"""Action class to conduct research and generate a research report."""
- name: str = "ConductResearch"
- context: Optional[str] = None
- llm: BaseLLM = Field(default_factory=LLM)
-
def __init__(self, **kwargs):
super().__init__(**kwargs)
- if CONFIG.model_for_researcher_report:
- self.llm.model = CONFIG.model_for_researcher_report
async def run(
self,
diff --git a/metagpt/actions/run_code.py b/metagpt/actions/run_code.py
index 30b06f1a6..3b84cc9f2 100644
--- a/metagpt/actions/run_code.py
+++ b/metagpt/actions/run_code.py
@@ -16,12 +16,12 @@
class.
"""
import subprocess
+from pathlib import Path
from typing import Tuple
from pydantic import Field
from metagpt.actions.action import Action
-from metagpt.config import CONFIG
from metagpt.logs import logger
from metagpt.schema import RunCodeContext, RunCodeResult
from metagpt.utils.exceptions import handle_exception
@@ -48,7 +48,7 @@ WRITE ONLY ONE WORD, Engineer OR QaEngineer OR NoOne, IN THIS SECTION.
You should fill in necessary instruction, status, send to, and finally return all content between the --- segment line.
"""
-CONTEXT = """
+TEMPLATE_CONTEXT = """
## Development Code File Name
{code_file_name}
## Development Code
@@ -77,7 +77,7 @@ standard errors:
class RunCode(Action):
name: str = "RunCode"
- context: RunCodeContext = Field(default_factory=RunCodeContext)
+ i_context: RunCodeContext = Field(default_factory=RunCodeContext)
@classmethod
async def run_text(cls, code) -> Tuple[str, str]:
@@ -89,13 +89,12 @@ class RunCode(Action):
return "", str(e)
return namespace.get("result", ""), ""
- @classmethod
- async def run_script(cls, working_directory, additional_python_paths=[], command=[]) -> Tuple[str, str]:
+ async def run_script(self, working_directory, additional_python_paths=[], command=[]) -> Tuple[str, str]:
working_directory = str(working_directory)
additional_python_paths = [str(path) for path in additional_python_paths]
# Copy the current environment variables
- env = CONFIG.new_environ()
+ env = self.context.new_environ()
# Modify the PYTHONPATH environment variable
additional_python_paths = [working_directory] + additional_python_paths
@@ -119,25 +118,25 @@ class RunCode(Action):
return stdout.decode("utf-8"), stderr.decode("utf-8")
async def run(self, *args, **kwargs) -> RunCodeResult:
- logger.info(f"Running {' '.join(self.context.command)}")
- if self.context.mode == "script":
+ logger.info(f"Running {' '.join(self.i_context.command)}")
+ if self.i_context.mode == "script":
outs, errs = await self.run_script(
- command=self.context.command,
- working_directory=self.context.working_directory,
- additional_python_paths=self.context.additional_python_paths,
+ command=self.i_context.command,
+ working_directory=self.i_context.working_directory,
+ additional_python_paths=self.i_context.additional_python_paths,
)
- elif self.context.mode == "text":
- outs, errs = await self.run_text(code=self.context.code)
+ elif self.i_context.mode == "text":
+ outs, errs = await self.run_text(code=self.i_context.code)
logger.info(f"{outs=}")
logger.info(f"{errs=}")
- context = CONTEXT.format(
- code=self.context.code,
- code_file_name=self.context.code_filename,
- test_code=self.context.test_code,
- test_file_name=self.context.test_filename,
- command=" ".join(self.context.command),
+ context = TEMPLATE_CONTEXT.format(
+ code=self.i_context.code,
+ code_file_name=self.i_context.code_filename,
+ test_code=self.i_context.test_code,
+ test_file_name=self.i_context.test_filename,
+ command=" ".join(self.i_context.command),
outs=outs[:500], # outs might be long but they are not important, truncate them to avoid token overflow
errs=errs[:10000], # truncate errors to avoid token overflow
)
@@ -152,11 +151,23 @@ class RunCode(Action):
return subprocess.run(cmd, check=check, cwd=cwd, env=env)
@staticmethod
- def _install_dependencies(working_directory, env):
+ def _install_requirements(working_directory, env):
+ file_path = Path(working_directory) / "requirements.txt"
+ if not file_path.exists():
+ return
+ if file_path.stat().st_size == 0:
+ return
install_command = ["python", "-m", "pip", "install", "-r", "requirements.txt"]
logger.info(" ".join(install_command))
RunCode._install_via_subprocess(install_command, check=True, cwd=working_directory, env=env)
+ @staticmethod
+ def _install_pytest(working_directory, env):
install_pytest_command = ["python", "-m", "pip", "install", "pytest"]
logger.info(" ".join(install_pytest_command))
RunCode._install_via_subprocess(install_pytest_command, check=True, cwd=working_directory, env=env)
+
+ @staticmethod
+ def _install_dependencies(working_directory, env):
+ RunCode._install_requirements(working_directory, env)
+ RunCode._install_pytest(working_directory, env)
diff --git a/metagpt/actions/search_and_summarize.py b/metagpt/actions/search_and_summarize.py
index d2e361f73..59b35cd58 100644
--- a/metagpt/actions/search_and_summarize.py
+++ b/metagpt/actions/search_and_summarize.py
@@ -8,10 +8,9 @@
from typing import Any, Optional
import pydantic
-from pydantic import Field, model_validator
+from pydantic import model_validator
from metagpt.actions import Action
-from metagpt.config import CONFIG, Config
from metagpt.logs import logger
from metagpt.schema import Message
from metagpt.tools import SearchEngineType
@@ -103,32 +102,25 @@ You are a member of a professional butler team and will provide helpful suggesti
"""
-# TOTEST
class SearchAndSummarize(Action):
name: str = ""
content: Optional[str] = None
- config: None = Field(default_factory=Config)
- engine: Optional[SearchEngineType] = CONFIG.search_engine
+ engine: Optional[SearchEngineType] = None
search_func: Optional[Any] = None
search_engine: SearchEngine = None
result: str = ""
- @model_validator(mode="before")
- @classmethod
- def validate_engine_and_run_func(cls, values):
- engine = values.get("engine")
- search_func = values.get("search_func")
- config = Config()
-
- if engine is None:
- engine = config.search_engine
+ @model_validator(mode="after")
+ def validate_engine_and_run_func(self):
+ if self.engine is None:
+ self.engine = self.config.search_engine
try:
- search_engine = SearchEngine(engine=engine, run_func=search_func)
+ search_engine = SearchEngine(engine=self.engine, run_func=self.search_func)
except pydantic.ValidationError:
search_engine = None
- values["search_engine"] = search_engine
- return values
+ self.search_engine = search_engine
+ return self
async def run(self, context: list[Message], system_text=SEARCH_AND_SUMMARIZE_SYSTEM) -> str:
if self.search_engine is None:
diff --git a/metagpt/actions/skill_action.py b/metagpt/actions/skill_action.py
index 301cebaab..b68596809 100644
--- a/metagpt/actions/skill_action.py
+++ b/metagpt/actions/skill_action.py
@@ -29,9 +29,7 @@ class ArgumentsParingAction(Action):
@property
def prompt(self):
- prompt = "You are a function parser. You can convert spoken words into function parameters.\n"
- prompt += "\n---\n"
- prompt += f"{self.skill.name} function parameters description:\n"
+ prompt = f"{self.skill.name} function parameters description:\n"
for k, v in self.skill.arguments.items():
prompt += f"parameter `{k}`: {v}\n"
prompt += "\n---\n"
@@ -49,7 +47,10 @@ class ArgumentsParingAction(Action):
async def run(self, with_message=None, **kwargs) -> Message:
prompt = self.prompt
- rsp = await self.llm.aask(msg=prompt, system_msgs=[])
+ rsp = await self.llm.aask(
+ msg=prompt,
+ system_msgs=["You are a function parser.", "You can convert spoken words into function parameters."],
+ )
logger.debug(f"SKILL:{prompt}\n, RESULT:{rsp}")
self.args = ArgumentsParingAction.parse_arguments(skill_name=self.skill.name, txt=rsp)
self.rsp = Message(content=rsp, role="assistant", instruct_content=self.args, cause_by=self)
diff --git a/metagpt/actions/summarize_code.py b/metagpt/actions/summarize_code.py
index bdad546d7..d21b62f83 100644
--- a/metagpt/actions/summarize_code.py
+++ b/metagpt/actions/summarize_code.py
@@ -11,11 +11,8 @@ from pydantic import Field
from tenacity import retry, stop_after_attempt, wait_random_exponential
from metagpt.actions.action import Action
-from metagpt.config import CONFIG
-from metagpt.const import SYSTEM_DESIGN_FILE_REPO, TASK_FILE_REPO
from metagpt.logs import logger
from metagpt.schema import CodeSummarizeContext
-from metagpt.utils.file_repository import FileRepository
PROMPT_TEMPLATE = """
NOTICE
@@ -29,9 +26,9 @@ ATTENTION: Use '##' to SPLIT SECTIONS, not '#'. Output format carefully referenc
{system_design}
```
-----
-# Tasks
+# Task
```text
-{tasks}
+{task}
```
-----
{code_blocks}
@@ -90,10 +87,9 @@ flowchart TB
"""
-# TOTEST
class SummarizeCode(Action):
name: str = "SummarizeCode"
- context: CodeSummarizeContext = Field(default_factory=CodeSummarizeContext)
+ i_context: CodeSummarizeContext = Field(default_factory=CodeSummarizeContext)
@retry(stop=stop_after_attempt(2), wait=wait_random_exponential(min=1, max=60))
async def summarize_code(self, prompt):
@@ -101,20 +97,20 @@ class SummarizeCode(Action):
return code_rsp
async def run(self):
- design_pathname = Path(self.context.design_filename)
- design_doc = await FileRepository.get_file(filename=design_pathname.name, relative_path=SYSTEM_DESIGN_FILE_REPO)
- task_pathname = Path(self.context.task_filename)
- task_doc = await FileRepository.get_file(filename=task_pathname.name, relative_path=TASK_FILE_REPO)
- src_file_repo = CONFIG.git_repo.new_file_repository(relative_path=CONFIG.src_workspace)
+ design_pathname = Path(self.i_context.design_filename)
+ design_doc = await self.repo.docs.system_design.get(filename=design_pathname.name)
+ task_pathname = Path(self.i_context.task_filename)
+ task_doc = await self.repo.docs.task.get(filename=task_pathname.name)
+ src_file_repo = self.repo.with_src_path(self.context.src_workspace).srcs
code_blocks = []
- for filename in self.context.codes_filenames:
+ for filename in self.i_context.codes_filenames:
code_doc = await src_file_repo.get(filename)
code_block = f"```python\n{code_doc.content}\n```\n-----"
code_blocks.append(code_block)
format_example = FORMAT_EXAMPLE
prompt = PROMPT_TEMPLATE.format(
system_design=design_doc.content,
- tasks=task_doc.content,
+ task=task_doc.content,
code_blocks="\n".join(code_blocks),
format_example=format_example,
)
diff --git a/metagpt/actions/talk_action.py b/metagpt/actions/talk_action.py
index dc127109c..0aac1c5a0 100644
--- a/metagpt/actions/talk_action.py
+++ b/metagpt/actions/talk_action.py
@@ -9,25 +9,31 @@
from typing import Optional
from metagpt.actions import Action
-from metagpt.config import CONFIG
-from metagpt.const import DEFAULT_LANGUAGE
+from metagpt.config2 import config
from metagpt.logs import logger
from metagpt.schema import Message
-# TOTEST
class TalkAction(Action):
- context: str
+ i_context: str
history_summary: str = ""
knowledge: str = ""
rsp: Optional[Message] = None
+ @property
+ def agent_description(self):
+ return self.context.kwargs.agent_description
+
+ @property
+ def language(self):
+ return self.context.kwargs.language or config.language
+
@property
def prompt(self):
prompt = ""
- if CONFIG.agent_description:
+ if self.agent_description:
prompt = (
- f"You are {CONFIG.agent_description}. Your responses should align with the role-play agreement, "
+ f"You are {self.agent_description}. Your responses should align with the role-play agreement, "
f"maintaining the character's persona and habits. When faced with unrelated questions, playfully "
f"decline to answer without revealing your AI nature to preserve the character's image.\n\n"
)
@@ -36,10 +42,10 @@ class TalkAction(Action):
prompt += (
"If the information is insufficient, you can search in the historical conversation or knowledge above.\n"
)
- language = CONFIG.language or DEFAULT_LANGUAGE
+ language = self.language
prompt += (
f"Answer the following questions strictly in {language}, and the answers must follow the Markdown format.\n "
- f"{self.context}"
+ f"{self.i_context}"
)
logger.debug(f"PROMPT: {prompt}")
return prompt
@@ -47,11 +53,11 @@ class TalkAction(Action):
@property
def prompt_gpt4(self):
kvs = {
- "{role}": CONFIG.agent_description or "",
+ "{role}": self.agent_description or "",
"{history}": self.history_summary or "",
"{knowledge}": self.knowledge or "",
- "{language}": CONFIG.language or DEFAULT_LANGUAGE,
- "{ask}": self.context,
+ "{language}": self.language,
+ "{ask}": self.i_context,
}
prompt = TalkActionPrompt.FORMATION_LOOSE
for k, v in kvs.items():
@@ -68,9 +74,9 @@ class TalkAction(Action):
@property
def aask_args(self):
- language = CONFIG.language or DEFAULT_LANGUAGE
+ language = self.language
system_msgs = [
- f"You are {CONFIG.agent_description}.",
+ f"You are {self.agent_description}.",
"Your responses should align with the role-play agreement, "
"maintaining the character's persona and habits. When faced with unrelated questions, playfully "
"decline to answer without revealing your AI nature to preserve the character's image.",
@@ -82,7 +88,7 @@ class TalkAction(Action):
format_msgs.append({"role": "assistant", "content": self.knowledge})
if self.history_summary:
format_msgs.append({"role": "assistant", "content": self.history_summary})
- return self.context, format_msgs, system_msgs
+ return self.i_context, format_msgs, system_msgs
async def run(self, with_message=None, **kwargs) -> Message:
msg, format_msgs, system_msgs = self.aask_args
diff --git a/metagpt/actions/write_analysis_code.py b/metagpt/actions/write_analysis_code.py
index c6e504b9e..5cea9fe51 100644
--- a/metagpt/actions/write_analysis_code.py
+++ b/metagpt/actions/write_analysis_code.py
@@ -4,19 +4,12 @@
@Author : orange-crow
@File : write_code_v2.py
"""
-import re
-from pathlib import Path
from typing import Dict, List, Tuple, Union
-from tenacity import retry, stop_after_attempt, wait_fixed
-
from metagpt.actions import Action
-from metagpt.llm import LLM
from metagpt.logs import logger
-from metagpt.prompts.ml_engineer import (
+from metagpt.prompts.write_analysis_code import (
CODE_GENERATOR_WITH_TOOLS,
- GENERATE_CODE_PROMPT,
- ML_TOOL_USAGE_PROMPT,
SELECT_FUNCTION_TOOLS,
TOOL_RECOMMENDATION_PROMPT,
TOOL_USAGE_PROMPT,
@@ -24,7 +17,7 @@ from metagpt.prompts.ml_engineer import (
from metagpt.schema import Message, Plan
from metagpt.tools import TOOL_REGISTRY
from metagpt.tools.tool_registry import validate_tool_names
-from metagpt.utils.common import create_func_config, remove_comments
+from metagpt.utils.common import create_func_config
class BaseWriteAnalysisCode(Action):
@@ -84,8 +77,8 @@ class WriteCodeByGenerate(BaseWriteAnalysisCode):
) -> dict:
# context.append(Message(content=self.REUSE_CODE_INSTRUCTION, role="user"))
prompt = self.process_msg(context, system_msg)
- code_content = await self.llm.aask_code(prompt, **kwargs)
- return code_content
+ rsp = await self.llm.aask_code(prompt, **kwargs)
+ return rsp
class WriteCodeWithTools(BaseWriteAnalysisCode):
@@ -195,133 +188,3 @@ class WriteCodeWithTools(BaseWriteAnalysisCode):
rsp = await self.llm.aask_code(prompt, **tool_config)
return rsp
-
-
-class WriteCodeWithToolsML(WriteCodeWithTools):
- async def run(
- self,
- context: List[Message],
- plan: Plan = None,
- column_info: str = "",
- **kwargs,
- ) -> Tuple[List[Message], str]:
- # prepare tool schemas and tool-type-specific instruction
- tool_schemas, tool_type_usage_prompt = await self._prepare_tools(plan=plan)
-
- # ML-specific variables to be used in prompt
- code_steps = plan.current_task.code_steps
- finished_tasks = plan.get_finished_tasks()
- code_context = [remove_comments(task.code) for task in finished_tasks]
- code_context = "\n\n".join(code_context)
-
- # prepare prompt depending on tool availability & LLM call
- if tool_schemas:
- prompt = ML_TOOL_USAGE_PROMPT.format(
- user_requirement=plan.goal,
- history_code=code_context,
- current_task=plan.current_task.instruction,
- column_info=column_info,
- tool_type_usage_prompt=tool_type_usage_prompt,
- code_steps=code_steps,
- tool_schemas=tool_schemas,
- )
-
- else:
- prompt = GENERATE_CODE_PROMPT.format(
- user_requirement=plan.goal,
- history_code=code_context,
- current_task=plan.current_task.instruction,
- column_info=column_info,
- tool_type_usage_prompt=tool_type_usage_prompt,
- code_steps=code_steps,
- )
- tool_config = create_func_config(CODE_GENERATOR_WITH_TOOLS)
- rsp = await self.llm.aask_code(prompt, **tool_config)
-
- # Extra output to be used for potential debugging
- context = [Message(content=prompt, role="user")]
-
- return context, rsp
-
-
-class MakeTools(WriteCodeByGenerate):
- DEFAULT_SYSTEM_MSG: str = """Convert any codes provied for you to a very General Function Code startswith `def`.\n
- **Notice:
- 1. Your code must contain a general function start with `def`.
- 2. Refactor your code to get the most efficient implementation for large input data in the shortest amount of time.
- 3. Must use Google style for function docstring, and your docstring must be consistent with the code,without missing anything.
- 4. Write example code after `if __name__ == '__main__':`by using old varibales in old code,
- and make sure it could be execute in the user's machine.
- 5. Only use the imported packages**
- """
-
- def __init__(self, name: str = "", context: list[Message] = None, llm: LLM = None, workspace: str = None):
- """
- :param str name: name, defaults to ''
- :param list[Message] context: context, defaults to None
- :param LLM llm: llm, defaults to None
- :param str workspace: tools code saved file path dir, defaults to None
- """
- super().__init__(name, context, llm)
- self.workspace = workspace or str(Path(__file__).parents[1].joinpath("./tools/functions/libs/udf"))
- self.file_suffix: str = ".py"
- self.context = []
-
- def parse_function_name(self, function_code: str) -> str:
- # 定义正则表达式模式
- pattern = r"\bdef\s+([a-zA-Z_]\w*)\s*\("
- # 在代码中搜索匹配的模式
- match = re.search(pattern, function_code)
- # 如果找到匹配项,则返回匹配的函数名;否则返回None
- if match:
- return match.group(1)
- else:
- return None
-
- def save(self, tool_code: str) -> None:
- func_name = self.parse_function_name(tool_code)
- if func_name is None:
- raise ValueError(f"No function name found in {tool_code}")
- saved_path = Path(self.workspace).joinpath(func_name + self.file_suffix)
- logger.info(f"Saved tool_code {func_name} in {str(saved_path)}.")
- saved_path.write_text(tool_code, encoding="utf-8")
-
- @retry(stop=stop_after_attempt(3), wait=wait_fixed(1))
- async def run(self, code: Union[str, List[dict]], code_desc: str = None, **kwargs) -> str:
- # 拼接code prompt
- code_prompt = f"The following code is about {code_desc}, convert it to be a General Function, {code}"
- if not self.context:
- self.context = self.process_msg(code_prompt)
- else:
- self.context.append(self.process_msg(code_prompt)[-1])
- logger.info(f"\n\nAsk to Make tools:\n{'-'*60}\n {self.context[-1]}")
-
- # 更新kwargs
- if "code" in kwargs:
- kwargs.pop("code")
- if "code_desc" in kwargs:
- kwargs.pop("code_desc")
-
- max_tries, current_try = 3, 0
- while True:
- tool_code = await self.llm.aask_code(self.context, **kwargs)
- func_name = self.parse_function_name(tool_code["code"])
- current_try += 1
- # make tools failed, add error message to context.
- if not func_name:
- logger.info(f"\n\nTools Respond\n{'-'*60}\n: {tool_code}")
- logger.error(f"No function name found in code, we will retry make tools.\n{tool_code['code']}\n")
- self.context.append(
- {"role": "user", "content": "We need a general function in above code,but not found function."}
- )
- # end make tools
- if func_name is not None or current_try >= max_tries:
- if current_try >= max_tries:
- logger.error(
- f"We have tried the maximum number of attempts {max_tries}\
- and still have not created tools successfully, we will skip it."
- )
- break
- logger.info(f"\n\nTools Respond\n{'-'*60}\n: {tool_code}")
- self.save(tool_code["code"])
- return tool_code["code"]
diff --git a/metagpt/actions/write_code.py b/metagpt/actions/write_code.py
index 7377442b5..0b86ac1bb 100644
--- a/metagpt/actions/write_code.py
+++ b/metagpt/actions/write_code.py
@@ -21,18 +21,17 @@ from pydantic import Field
from tenacity import retry, stop_after_attempt, wait_random_exponential
from metagpt.actions.action import Action
-from metagpt.config import CONFIG
+from metagpt.actions.project_management_an import REFINED_TASK_LIST, TASK_LIST
+from metagpt.actions.write_code_plan_and_change_an import REFINED_TEMPLATE
from metagpt.const import (
BUGFIX_FILENAME,
- CODE_SUMMARIES_FILE_REPO,
- DOCS_FILE_REPO,
- TASK_FILE_REPO,
- TEST_OUTPUTS_FILE_REPO,
+ CODE_PLAN_AND_CHANGE_FILENAME,
+ REQUIREMENT_FILENAME,
)
from metagpt.logs import logger
from metagpt.schema import CodingContext, Document, RunCodeResult
from metagpt.utils.common import CodeParser
-from metagpt.utils.file_repository import FileRepository
+from metagpt.utils.project_repo import ProjectRepo
PROMPT_TEMPLATE = """
NOTICE
@@ -44,8 +43,8 @@ ATTENTION: Use '##' to SPLIT SECTIONS, not '#'. Output format carefully referenc
## Design
{design}
-## Tasks
-{tasks}
+## Task
+{task}
## Legacy Code
```Code
@@ -87,7 +86,7 @@ ATTENTION: Use '##' to SPLIT SECTIONS, not '#'. Output format carefully referenc
class WriteCode(Action):
name: str = "WriteCode"
- context: Document = Field(default_factory=Document)
+ i_context: Document = Field(default_factory=Document)
@retry(wait=wait_random_exponential(min=1, max=60), stop=stop_after_attempt(6))
async def write_code(self, prompt) -> str:
@@ -96,16 +95,15 @@ class WriteCode(Action):
return code
async def run(self, *args, **kwargs) -> CodingContext:
- bug_feedback = await FileRepository.get_file(filename=BUGFIX_FILENAME, relative_path=DOCS_FILE_REPO)
- coding_context = CodingContext.loads(self.context.content)
- test_doc = await FileRepository.get_file(
- filename="test_" + coding_context.filename + ".json", relative_path=TEST_OUTPUTS_FILE_REPO
- )
+ bug_feedback = await self.repo.docs.get(filename=BUGFIX_FILENAME)
+ coding_context = CodingContext.loads(self.i_context.content)
+ test_doc = await self.repo.test_outputs.get(filename="test_" + coding_context.filename + ".json")
+ code_plan_and_change_doc = await self.repo.docs.code_plan_and_change.get(filename=CODE_PLAN_AND_CHANGE_FILENAME)
+ code_plan_and_change = code_plan_and_change_doc.content if code_plan_and_change_doc else ""
+ requirement_doc = await self.repo.docs.get(filename=REQUIREMENT_FILENAME)
summary_doc = None
if coding_context.design_doc and coding_context.design_doc.filename:
- summary_doc = await FileRepository.get_file(
- filename=coding_context.design_doc.filename, relative_path=CODE_SUMMARIES_FILE_REPO
- )
+ summary_doc = await self.repo.docs.code_summary.get(filename=coding_context.design_doc.filename)
logs = ""
if test_doc:
test_detail = RunCodeResult.loads(test_doc.content)
@@ -113,42 +111,109 @@ class WriteCode(Action):
if bug_feedback:
code_context = coding_context.code_doc.content
+ elif code_plan_and_change:
+ code_context = await self.get_codes(
+ coding_context.task_doc, exclude=self.i_context.filename, project_repo=self.repo, use_inc=True
+ )
else:
- code_context = await self.get_codes(coding_context.task_doc, exclude=self.context.filename)
+ code_context = await self.get_codes(
+ coding_context.task_doc,
+ exclude=self.i_context.filename,
+ project_repo=self.repo.with_src_path(self.context.src_workspace),
+ )
- prompt = PROMPT_TEMPLATE.format(
- design=coding_context.design_doc.content if coding_context.design_doc else "",
- tasks=coding_context.task_doc.content if coding_context.task_doc else "",
- code=code_context,
- logs=logs,
- feedback=bug_feedback.content if bug_feedback else "",
- filename=self.context.filename,
- summary_log=summary_doc.content if summary_doc else "",
- )
+ if code_plan_and_change:
+ prompt = REFINED_TEMPLATE.format(
+ user_requirement=requirement_doc.content if requirement_doc else "",
+ code_plan_and_change=code_plan_and_change,
+ design=coding_context.design_doc.content if coding_context.design_doc else "",
+ task=coding_context.task_doc.content if coding_context.task_doc else "",
+ code=code_context,
+ logs=logs,
+ feedback=bug_feedback.content if bug_feedback else "",
+ filename=self.i_context.filename,
+ summary_log=summary_doc.content if summary_doc else "",
+ )
+ else:
+ prompt = PROMPT_TEMPLATE.format(
+ design=coding_context.design_doc.content if coding_context.design_doc else "",
+ task=coding_context.task_doc.content if coding_context.task_doc else "",
+ code=code_context,
+ logs=logs,
+ feedback=bug_feedback.content if bug_feedback else "",
+ filename=self.i_context.filename,
+ summary_log=summary_doc.content if summary_doc else "",
+ )
logger.info(f"Writing {coding_context.filename}..")
code = await self.write_code(prompt)
if not coding_context.code_doc:
# avoid root_path pydantic ValidationError if use WriteCode alone
- root_path = CONFIG.src_workspace if CONFIG.src_workspace else ""
+ root_path = self.context.src_workspace if self.context.src_workspace else ""
coding_context.code_doc = Document(filename=coding_context.filename, root_path=str(root_path))
coding_context.code_doc.content = code
return coding_context
@staticmethod
- async def get_codes(task_doc, exclude) -> str:
+ async def get_codes(task_doc: Document, exclude: str, project_repo: ProjectRepo, use_inc: bool = False) -> str:
+ """
+ Get codes for generating the exclude file in various scenarios.
+
+ Attributes:
+ task_doc (Document): Document object of the task file.
+ exclude (str): The file to be generated. Specifies the filename to be excluded from the code snippets.
+ project_repo (ProjectRepo): ProjectRepo object of the project.
+ use_inc (bool): Indicates whether the scenario involves incremental development. Defaults to False.
+
+ Returns:
+ str: Codes for generating the exclude file.
+ """
if not task_doc:
return ""
if not task_doc.content:
- task_doc.content = FileRepository.get_file(filename=task_doc.filename, relative_path=TASK_FILE_REPO)
+ task_doc = project_repo.docs.task.get(filename=task_doc.filename)
m = json.loads(task_doc.content)
- code_filenames = m.get("Task list", [])
+ code_filenames = m.get(TASK_LIST.key, []) if use_inc else m.get(REFINED_TASK_LIST.key, [])
codes = []
- src_file_repo = CONFIG.git_repo.new_file_repository(relative_path=CONFIG.src_workspace)
- for filename in code_filenames:
- if filename == exclude:
- continue
- doc = await src_file_repo.get(filename=filename)
- if not doc:
- continue
- codes.append(f"----- {filename}\n" + doc.content)
+ src_file_repo = project_repo.srcs
+
+ # Incremental development scenario
+ if use_inc:
+ src_files = src_file_repo.all_files
+ # Get the old workspace contained the old codes and old workspace are created in previous CodePlanAndChange
+ old_file_repo = project_repo.git_repo.new_file_repository(relative_path=project_repo.old_workspace)
+ old_files = old_file_repo.all_files
+ # Get the union of the files in the src and old workspaces
+ union_files_list = list(set(src_files) | set(old_files))
+ for filename in union_files_list:
+ # Exclude the current file from the all code snippets
+ if filename == exclude:
+ # If the file is in the old workspace, use the old code
+ # Exclude unnecessary code to maintain a clean and focused main.py file, ensuring only relevant and
+ # essential functionality is included for the project’s requirements
+ if filename in old_files and filename != "main.py":
+ # Use old code
+ doc = await old_file_repo.get(filename=filename)
+ # If the file is in the src workspace, skip it
+ else:
+ continue
+ codes.insert(0, f"-----Now, {filename} to be rewritten\n```{doc.content}```\n=====")
+ # The code snippets are generated from the src workspace
+ else:
+ doc = await src_file_repo.get(filename=filename)
+ # If the file does not exist in the src workspace, skip it
+ if not doc:
+ continue
+ codes.append(f"----- {filename}\n```{doc.content}```")
+
+ # Normal scenario
+ else:
+ for filename in code_filenames:
+ # Exclude the current file to get the code snippets for generating the current file
+ if filename == exclude:
+ continue
+ doc = await src_file_repo.get(filename=filename)
+ if not doc:
+ continue
+ codes.append(f"----- {filename}\n```{doc.content}```")
+
return "\n".join(codes)
diff --git a/metagpt/actions/write_code_an_draft.py b/metagpt/actions/write_code_an_draft.py
index 968c8924b..ce030b0e9 100644
--- a/metagpt/actions/write_code_an_draft.py
+++ b/metagpt/actions/write_code_an_draft.py
@@ -5,7 +5,7 @@
@File : write_review.py
"""
import asyncio
-from typing import List
+from typing import List, Literal
from metagpt.actions import Action
from metagpt.actions.action_node import ActionNode
@@ -21,16 +21,15 @@ REVIEW = ActionNode(
],
)
-LGTM = ActionNode(
- key="LGTM",
- expected_type=str,
- instruction="LGTM/LBTM. If the code is fully implemented, "
- "give a LGTM (Looks Good To Me), otherwise provide a LBTM (Looks Bad To Me).",
+REVIEW_RESULT = ActionNode(
+ key="ReviewResult",
+ expected_type=Literal["LGTM", "LBTM"],
+ instruction="LGTM/LBTM. If the code is fully implemented, " "give a LGTM, otherwise provide a LBTM.",
example="LBTM",
)
-ACTIONS = ActionNode(
- key="Actions",
+NEXT_STEPS = ActionNode(
+ key="NextSteps",
expected_type=str,
instruction="Based on the code review outcome, suggest actionable steps. This can include code changes, "
"refactoring suggestions, or any follow-up tasks.",
@@ -69,7 +68,7 @@ WRITE_DRAFT = ActionNode(
)
-WRITE_MOVE_FUNCTION = ActionNode(
+WRITE_FUNCTION = ActionNode(
key="WriteFunction",
expected_type=str,
instruction="write code for the function not implemented.",
@@ -555,8 +554,8 @@ LBTM
"""
-WRITE_CODE_NODE = ActionNode.from_children("WRITE_REVIEW_NODE", [REVIEW, LGTM, ACTIONS])
-WRITE_MOVE_NODE = ActionNode.from_children("WRITE_MOVE_NODE", [WRITE_DRAFT, WRITE_MOVE_FUNCTION])
+WRITE_CODE_NODE = ActionNode.from_children("WRITE_REVIEW_NODE", [REVIEW, REVIEW_RESULT, NEXT_STEPS])
+WRITE_MOVE_NODE = ActionNode.from_children("WRITE_MOVE_NODE", [WRITE_DRAFT, WRITE_FUNCTION])
CR_FOR_MOVE_FUNCTION_BY_3 = """
@@ -579,8 +578,7 @@ class WriteCodeAN(Action):
async def run(self, context):
self.llm.system_prompt = "You are an outstanding engineer and can implement any code"
- return await WRITE_MOVE_FUNCTION.fill(context=context, llm=self.llm, schema="json")
- # return await WRITE_CODE_NODE.fill(context=context, llm=self.llm, schema="markdown")
+ return await WRITE_MOVE_NODE.fill(context=context, llm=self.llm, schema="json")
async def main():
diff --git a/metagpt/actions/write_code_plan_and_change_an.py b/metagpt/actions/write_code_plan_and_change_an.py
new file mode 100644
index 000000000..708808050
--- /dev/null
+++ b/metagpt/actions/write_code_plan_and_change_an.py
@@ -0,0 +1,210 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+"""
+@Time : 2023/12/26
+@Author : mannaandpoem
+@File : write_code_plan_and_change_an.py
+"""
+import os
+
+from pydantic import Field
+
+from metagpt.actions.action import Action
+from metagpt.actions.action_node import ActionNode
+from metagpt.schema import CodePlanAndChangeContext
+
+CODE_PLAN_AND_CHANGE = ActionNode(
+ key="Code Plan And Change",
+ expected_type=str,
+ instruction="Developing comprehensive and step-by-step incremental development plan, and write Incremental "
+ "Change by making a code draft that how to implement incremental development including detailed steps based on the "
+ "context. Note: Track incremental changes using mark of '+' or '-' for add/modify/delete code, and conforms to the "
+ "output format of git diff",
+ example="""
+1. Plan for calculator.py: Enhance the functionality of `calculator.py` by extending it to incorporate methods for subtraction, multiplication, and division. Additionally, implement robust error handling for the division operation to mitigate potential issues related to division by zero.
+```python
+class Calculator:
+ self.result = number1 + number2
+ return self.result
+
+- def sub(self, number1, number2) -> float:
++ def subtract(self, number1: float, number2: float) -> float:
++ '''
++ Subtracts the second number from the first and returns the result.
++
++ Args:
++ number1 (float): The number to be subtracted from.
++ number2 (float): The number to subtract.
++
++ Returns:
++ float: The difference of number1 and number2.
++ '''
++ self.result = number1 - number2
++ return self.result
++
+ def multiply(self, number1: float, number2: float) -> float:
+- pass
++ '''
++ Multiplies two numbers and returns the result.
++
++ Args:
++ number1 (float): The first number to multiply.
++ number2 (float): The second number to multiply.
++
++ Returns:
++ float: The product of number1 and number2.
++ '''
++ self.result = number1 * number2
++ return self.result
++
+ def divide(self, number1: float, number2: float) -> float:
+- pass
++ '''
++ ValueError: If the second number is zero.
++ '''
++ if number2 == 0:
++ raise ValueError('Cannot divide by zero')
++ self.result = number1 / number2
++ return self.result
++
+- def reset_result(self):
++ def clear(self):
++ if self.result != 0.0:
++ print("Result is not zero, clearing...")
++ else:
++ print("Result is already zero, no need to clear.")
++
+ self.result = 0.0
+```
+
+2. Plan for main.py: Integrate new API endpoints for subtraction, multiplication, and division into the existing codebase of `main.py`. Then, ensure seamless integration with the overall application architecture and maintain consistency with coding standards.
+```python
+def add_numbers():
+ result = calculator.add_numbers(num1, num2)
+ return jsonify({'result': result}), 200
+
+-# TODO: Implement subtraction, multiplication, and division operations
++@app.route('/subtract_numbers', methods=['POST'])
++def subtract_numbers():
++ data = request.get_json()
++ num1 = data.get('num1', 0)
++ num2 = data.get('num2', 0)
++ result = calculator.subtract_numbers(num1, num2)
++ return jsonify({'result': result}), 200
++
++@app.route('/multiply_numbers', methods=['POST'])
++def multiply_numbers():
++ data = request.get_json()
++ num1 = data.get('num1', 0)
++ num2 = data.get('num2', 0)
++ try:
++ result = calculator.divide_numbers(num1, num2)
++ except ValueError as e:
++ return jsonify({'error': str(e)}), 400
++ return jsonify({'result': result}), 200
++
+ if __name__ == '__main__':
+ app.run()
+```""",
+)
+
+CODE_PLAN_AND_CHANGE_CONTEXT = """
+## User New Requirements
+{requirement}
+
+## PRD
+{prd}
+
+## Design
+{design}
+
+## Task
+{task}
+
+## Legacy Code
+{code}
+"""
+
+REFINED_TEMPLATE = """
+NOTICE
+Role: You are a professional engineer; The main goal is to complete incremental development by combining legacy code and plan and Incremental Change, ensuring the integration of new features.
+
+# Context
+## User New Requirements
+{user_requirement}
+
+## Code Plan And Change
+{code_plan_and_change}
+
+## Design
+{design}
+
+## Task
+{task}
+
+## Legacy Code
+```Code
+{code}
+```
+
+## Debug logs
+```text
+{logs}
+
+{summary_log}
+```
+
+## Bug Feedback logs
+```text
+{feedback}
+```
+
+# Format example
+## Code: {filename}
+```python
+## {filename}
+...
+```
+
+# Instruction: Based on the context, follow "Format example", write or rewrite code.
+## Write/Rewrite Code: Only write one file {filename}, write or rewrite complete code using triple quotes based on the following attentions and context.
+1. Only One file: do your best to implement THIS ONLY ONE FILE.
+2. COMPLETE CODE: Your code will be part of the entire project, so please implement complete, reliable, reusable code snippets.
+3. Set default value: If there is any setting, ALWAYS SET A DEFAULT VALUE, ALWAYS USE STRONG TYPE AND EXPLICIT VARIABLE. AVOID circular import.
+4. Follow design: YOU MUST FOLLOW "Data structures and interfaces". DONT CHANGE ANY DESIGN. Do not use public member functions that do not exist in your design.
+5. Follow Code Plan And Change: If there is any Incremental Change that is marked by the git diff format using '+' and '-' for add/modify/delete code, or Legacy Code files contain "{filename} to be rewritten", you must merge it into the code file according to the plan.
+6. CAREFULLY CHECK THAT YOU DONT MISS ANY NECESSARY CLASS/FUNCTION IN THIS FILE.
+7. Before using a external variable/module, make sure you import it first.
+8. Write out EVERY CODE DETAIL, DON'T LEAVE TODO.
+9. Attention: Retain details that are not related to incremental development but are important for maintaining the consistency and clarity of the old code.
+"""
+
+WRITE_CODE_PLAN_AND_CHANGE_NODE = ActionNode.from_children("WriteCodePlanAndChange", [CODE_PLAN_AND_CHANGE])
+
+
+class WriteCodePlanAndChange(Action):
+ name: str = "WriteCodePlanAndChange"
+ i_context: CodePlanAndChangeContext = Field(default_factory=CodePlanAndChangeContext)
+
+ async def run(self, *args, **kwargs):
+ self.llm.system_prompt = "You are a professional software engineer, your primary responsibility is to "
+ "meticulously craft comprehensive incremental development plan and deliver detailed incremental change"
+ prd_doc = await self.repo.docs.prd.get(filename=self.i_context.prd_filename)
+ design_doc = await self.repo.docs.system_design.get(filename=self.i_context.design_filename)
+ task_doc = await self.repo.docs.task.get(filename=self.i_context.task_filename)
+ code_text = await self.get_old_codes()
+ context = CODE_PLAN_AND_CHANGE_CONTEXT.format(
+ requirement=self.i_context.requirement,
+ prd=prd_doc.content,
+ design=design_doc.content,
+ task=task_doc.content,
+ code=code_text,
+ )
+ return await WRITE_CODE_PLAN_AND_CHANGE_NODE.fill(context=context, llm=self.llm, schema="json")
+
+ async def get_old_codes(self) -> str:
+ self.repo.old_workspace = self.repo.git_repo.workdir / os.path.basename(self.config.project_path)
+ old_file_repo = self.repo.git_repo.new_file_repository(relative_path=self.repo.old_workspace)
+ old_codes = await old_file_repo.get_all()
+ codes = [f"----- {code.filename}\n```{code.content}```" for code in old_codes]
+ return "\n".join(codes)
diff --git a/metagpt/actions/write_code_review.py b/metagpt/actions/write_code_review.py
index a8c913573..da636eb36 100644
--- a/metagpt/actions/write_code_review.py
+++ b/metagpt/actions/write_code_review.py
@@ -13,7 +13,7 @@ from tenacity import retry, stop_after_attempt, wait_random_exponential
from metagpt.actions import WriteCode
from metagpt.actions.action import Action
-from metagpt.config import CONFIG
+from metagpt.const import CODE_PLAN_AND_CHANGE_FILENAME, REQUIREMENT_FILENAME
from metagpt.logs import logger
from metagpt.schema import CodingContext
from metagpt.utils.common import CodeParser
@@ -120,7 +120,7 @@ REWRITE_CODE_TEMPLATE = """
class WriteCodeReview(Action):
name: str = "WriteCodeReview"
- context: CodingContext = Field(default_factory=CodingContext)
+ i_context: CodingContext = Field(default_factory=CodingContext)
@retry(wait=wait_random_exponential(min=1, max=60), stop=stop_after_attempt(6))
async def write_code_review_and_rewrite(self, context_prompt, cr_prompt, filename):
@@ -136,41 +136,64 @@ class WriteCodeReview(Action):
return result, code
async def run(self, *args, **kwargs) -> CodingContext:
- iterative_code = self.context.code_doc.content
- k = CONFIG.code_review_k_times or 1
+ iterative_code = self.i_context.code_doc.content
+ k = self.context.config.code_review_k_times or 1
+
for i in range(k):
- format_example = FORMAT_EXAMPLE.format(filename=self.context.code_doc.filename)
- task_content = self.context.task_doc.content if self.context.task_doc else ""
- code_context = await WriteCode.get_codes(self.context.task_doc, exclude=self.context.filename)
- context = "\n".join(
- [
- "## System Design\n" + str(self.context.design_doc) + "\n",
- "## Tasks\n" + task_content + "\n",
- "## Code Files\n" + code_context + "\n",
- ]
+ format_example = FORMAT_EXAMPLE.format(filename=self.i_context.code_doc.filename)
+ task_content = self.i_context.task_doc.content if self.i_context.task_doc else ""
+ code_context = await WriteCode.get_codes(
+ self.i_context.task_doc,
+ exclude=self.i_context.filename,
+ project_repo=self.repo.with_src_path(self.context.src_workspace),
+ use_inc=self.config.inc,
)
+
+ if not self.config.inc:
+ context = "\n".join(
+ [
+ "## System Design\n" + str(self.i_context.design_doc) + "\n",
+ "## Task\n" + task_content + "\n",
+ "## Code Files\n" + code_context + "\n",
+ ]
+ )
+ else:
+ requirement_doc = await self.repo.docs.get(filename=REQUIREMENT_FILENAME)
+ code_plan_and_change_doc = await self.repo.get(filename=CODE_PLAN_AND_CHANGE_FILENAME)
+ context = "\n".join(
+ [
+ "## User New Requirements\n" + str(requirement_doc) + "\n",
+ "## Code Plan And Change\n" + str(code_plan_and_change_doc) + "\n",
+ "## System Design\n" + str(self.i_context.design_doc) + "\n",
+ "## Task\n" + task_content + "\n",
+ "## Code Files\n" + code_context + "\n",
+ ]
+ )
+
context_prompt = PROMPT_TEMPLATE.format(
context=context,
code=iterative_code,
- filename=self.context.code_doc.filename,
+ filename=self.i_context.code_doc.filename,
)
cr_prompt = EXAMPLE_AND_INSTRUCTION.format(
format_example=format_example,
)
+ len1 = len(iterative_code) if iterative_code else 0
+ len2 = len(self.i_context.code_doc.content) if self.i_context.code_doc.content else 0
logger.info(
- f"Code review and rewrite {self.context.code_doc.filename}: {i + 1}/{k} | {len(iterative_code)=}, "
- f"{len(self.context.code_doc.content)=}"
+ f"Code review and rewrite {self.i_context.code_doc.filename}: {i + 1}/{k} | len(iterative_code)={len1}, "
+ f"len(self.i_context.code_doc.content)={len2}"
)
result, rewrited_code = await self.write_code_review_and_rewrite(
- context_prompt, cr_prompt, self.context.code_doc.filename
+ context_prompt, cr_prompt, self.i_context.code_doc.filename
)
if "LBTM" in result:
iterative_code = rewrited_code
elif "LGTM" in result:
- self.context.code_doc.content = iterative_code
- return self.context
+ self.i_context.code_doc.content = iterative_code
+ return self.i_context
# code_rsp = await self._aask_v1(prompt, "code_rsp", OUTPUT_MAPPING)
# self._save(context, filename, code)
# 如果rewrited_code是None(原code perfect),那么直接返回code
- self.context.code_doc.content = iterative_code
- return self.context
+ self.i_context.code_doc.content = iterative_code
+ return self.i_context
diff --git a/metagpt/actions/write_code_steps.py b/metagpt/actions/write_code_steps.py
deleted file mode 100644
index 7ba22fde4..000000000
--- a/metagpt/actions/write_code_steps.py
+++ /dev/null
@@ -1,116 +0,0 @@
-import json
-
-from metagpt.actions import Action
-from metagpt.schema import Plan
-from metagpt.utils.common import CodeParser
-
-# CODE_STEPS_PROMPT_TEMPLATE = """
-# # Context
-# {context}
-#
-# -----
-# Tasks are all code development tasks.
-# You are a professional engineer, the main goal is to plan out concise solution steps for Current Task before coding.
-# A planning process can reduce the difficulty and improve the quality of coding.
-# You may be given some code plans for the tasks ahead, but you don't have to follow the existing plan when planning the current task.
-# The output plan should following the subsequent principles:
-# 1.The plan is a rough checklist of steps outlining the entire program's structure.Try to keep the number of steps fewer than 5.
-# 2.The steps should be written concisely and at a high level, avoiding overly detailed implementation specifics.
-# 3.The execution of the plan happens sequentially, but the plan can incorporate conditional (if) and looping(loop) keywords for more complex structures.
-#
-# Output the code steps in a JSON format, as shown in this example:
-# ```json
-# {
-# "Step 1": "",
-# "Step 2": "",
-# "Step 3": "",
-# ...
-# }
-# ```
-# """
-
-CODE_STEPS_PROMPT_TEMPLATE = """
-# Context
-{context}
-
------
-Tasks are all code development tasks.
-You are a professional engineer, the main goal is to plan out concise solution steps for Current Task before coding.
-A planning process can reduce the difficulty and improve the quality of coding.
-You may be given some code plans for the tasks ahead, but you don't have to follow the existing plan when planning the current task.
-The output plan should following the subsequent principles:
-1.The plan is a rough checklist of steps outlining the entire program's structure.Try to keep the number of steps fewer than 5.
-2.The steps should be written concisely and at a high level, avoiding overly detailed implementation specifics.
-3.The execution of the plan happens sequentially, but the plan can incorporate conditional (if) and looping(loop) keywords for more complex structures.
-4.Design and provide code steps by following the code logic. Analyze the provided code step by step and reuse the imported library.
-
-Output the code steps in a JSON format, as shown in this example:
-```json
-{
- "Step 1": "",
- "Step 2": "",
- "Step 3": "",
- ...
-}
-```
-"""
-
-# STRUCTURAL_CONTEXT = """
-# ## User Requirement
-# {user_requirement}
-# ## Current Plan
-# {tasks}
-# ## Current Task
-# {current_task}
-# """
-
-STRUCTURAL_CONTEXT = """
-## User Requirement
-{user_requirement}
-## Plan
-{tasks}
-## Codes
-{codes}
-## Current Task
-{current_task}
-"""
-
-
-class WriteCodeSteps(Action):
- async def run(self, plan: Plan) -> str:
- """Run of a task guide writing action, used in ml engineer
-
- Args:
- plan (plan): task plan
- useful_memories (list): useful_memories
- Returns:
- str: The dataset_descriptions string.
- """
-
- context = self.get_context(plan)
- code_steps_prompt = CODE_STEPS_PROMPT_TEMPLATE.replace("{context}", context)
- code_steps = await self._aask(code_steps_prompt)
- code_steps = CodeParser.parse_code(block=None, text=code_steps)
- return code_steps
-
- def get_context(self, plan: Plan):
- user_requirement = plan.goal
- # select_task_keys = ['task_id', 'instruction', 'is_finished', 'code']
- # select_task_keys = ['task_id','instruction']
-
- def process_task(task):
- task_dict = task.dict()
- # ptask = {k: task_dict[k] for k in task_dict if k in select_task_keys }
- ptask = f"task_id_{task_dict['task_id']}:{task_dict['instruction']}"
- return ptask
-
- tasks = json.dumps([process_task(task) for task in plan.tasks], indent=4, ensure_ascii=False)
-
- code_lists = [task.code for task in plan.tasks if task.is_finished == True]
- codes = "\n\n".join(code_lists)
- current_task = json.dumps(process_task(plan.current_task)) if plan.current_task else {}
- context = STRUCTURAL_CONTEXT.format(
- user_requirement=user_requirement, tasks=tasks, codes=codes, current_task=current_task
- )
- # print(context)
- return context
diff --git a/metagpt/actions/write_docstring.py b/metagpt/actions/write_docstring.py
index 8b8335517..79204e6a4 100644
--- a/metagpt/actions/write_docstring.py
+++ b/metagpt/actions/write_docstring.py
@@ -161,7 +161,7 @@ class WriteDocstring(Action):
"""
desc: str = "Write docstring for code."
- context: Optional[str] = None
+ i_context: Optional[str] = None
async def run(
self,
diff --git a/metagpt/actions/write_plan.py b/metagpt/actions/write_plan.py
index 60dcef43b..335a09841 100644
--- a/metagpt/actions/write_plan.py
+++ b/metagpt/actions/write_plan.py
@@ -10,7 +10,10 @@ from typing import Dict, List, Tuple
from metagpt.actions import Action
from metagpt.logs import logger
-from metagpt.prompts.ml_engineer import ASSIGN_TASK_TYPE_CONFIG, ASSIGN_TASK_TYPE_PROMPT
+from metagpt.prompts.write_analysis_code import (
+ ASSIGN_TASK_TYPE_CONFIG,
+ ASSIGN_TASK_TYPE_PROMPT,
+)
from metagpt.schema import Message, Plan, Task
from metagpt.tools import TOOL_REGISTRY
from metagpt.utils.common import CodeParser, create_func_config
diff --git a/metagpt/actions/write_prd.py b/metagpt/actions/write_prd.py
index 073d8c076..823786893 100644
--- a/metagpt/actions/write_prd.py
+++ b/metagpt/actions/write_prd.py
@@ -14,26 +14,22 @@
from __future__ import annotations
import json
-import uuid
from pathlib import Path
-from typing import Optional
from metagpt.actions import Action, ActionOutput
from metagpt.actions.action_node import ActionNode
from metagpt.actions.fix_bug import FixBug
from metagpt.actions.write_prd_an import (
+ COMPETITIVE_QUADRANT_CHART,
PROJECT_NAME,
+ REFINED_PRD_NODE,
WP_IS_RELATIVE_NODE,
WP_ISSUE_TYPE_NODE,
WRITE_PRD_NODE,
)
-from metagpt.config import CONFIG
from metagpt.const import (
BUGFIX_FILENAME,
COMPETITIVE_ANALYSIS_FILE_REPO,
- DOCS_FILE_REPO,
- PRD_PDF_FILE_REPO,
- PRDS_FILE_REPO,
REQUIREMENT_FILENAME,
)
from metagpt.logs import logger
@@ -63,135 +59,114 @@ NEW_REQ_TEMPLATE = """
class WritePRD(Action):
- name: str = "WritePRD"
- content: Optional[str] = None
+ """WritePRD deal with the following situations:
+ 1. Bugfix: If the requirement is a bugfix, the bugfix document will be generated.
+ 2. New requirement: If the requirement is a new requirement, the PRD document will be generated.
+ 3. Requirement update: If the requirement is an update, the PRD document will be updated.
+ """
- async def run(self, with_messages, schema=CONFIG.prompt_schema, *args, **kwargs) -> ActionOutput | Message:
- # Determine which requirement documents need to be rewritten: Use LLM to assess whether new requirements are
- # related to the PRD. If they are related, rewrite the PRD.
- docs_file_repo = CONFIG.git_repo.new_file_repository(relative_path=DOCS_FILE_REPO)
- requirement_doc = await docs_file_repo.get(filename=REQUIREMENT_FILENAME)
- if requirement_doc and await self._is_bugfix(requirement_doc.content):
- await docs_file_repo.save(filename=BUGFIX_FILENAME, content=requirement_doc.content)
- await docs_file_repo.save(filename=REQUIREMENT_FILENAME, content="")
- bug_fix = BugFixContext(filename=BUGFIX_FILENAME)
- return Message(
- content=bug_fix.model_dump_json(),
- instruct_content=bug_fix,
- role="",
- cause_by=FixBug,
- sent_from=self,
- send_to="Alex", # the name of Engineer
- )
+ async def run(self, with_messages, *args, **kwargs) -> ActionOutput | Message:
+ """Run the action."""
+ req: Document = await self.repo.requirement
+ docs: list[Document] = await self.repo.docs.prd.get_all()
+ if not req:
+ raise FileNotFoundError("No requirement document found.")
+
+ if await self._is_bugfix(req.content):
+ logger.info(f"Bugfix detected: {req.content}")
+ return await self._handle_bugfix(req)
+ # remove bugfix file from last round in case of conflict
+ await self.repo.docs.delete(filename=BUGFIX_FILENAME)
+
+ # if requirement is related to other documents, update them, otherwise create a new one
+ if related_docs := await self.get_related_docs(req, docs):
+ logger.info(f"Requirement update detected: {req.content}")
+ return await self._handle_requirement_update(req, related_docs)
else:
- await docs_file_repo.delete(filename=BUGFIX_FILENAME)
+ logger.info(f"New requirement detected: {req.content}")
+ return await self._handle_new_requirement(req)
- prds_file_repo = CONFIG.git_repo.new_file_repository(PRDS_FILE_REPO)
- prd_docs = await prds_file_repo.get_all()
- change_files = Documents()
- for prd_doc in prd_docs:
- prd_doc = await self._update_prd(
- requirement_doc=requirement_doc, prd_doc=prd_doc, prds_file_repo=prds_file_repo, *args, **kwargs
- )
- if not prd_doc:
- continue
- change_files.docs[prd_doc.filename] = prd_doc
- logger.info(f"rewrite prd: {prd_doc.filename}")
- # If there is no existing PRD, generate one using 'docs/requirement.txt'.
- if not change_files.docs:
- prd_doc = await self._update_prd(
- requirement_doc=requirement_doc, prd_doc=None, prds_file_repo=prds_file_repo, *args, **kwargs
- )
- if prd_doc:
- change_files.docs[prd_doc.filename] = prd_doc
- logger.debug(f"new prd: {prd_doc.filename}")
- # Once all files under 'docs/prds/' have been compared with the newly added requirements, trigger the
- # 'publish' message to transition the workflow to the next stage. This design allows room for global
- # optimization in subsequent steps.
- return ActionOutput(content=change_files.model_dump_json(), instruct_content=change_files)
+ async def _handle_bugfix(self, req: Document) -> Message:
+ # ... bugfix logic ...
+ await self.repo.docs.save(filename=BUGFIX_FILENAME, content=req.content)
+ await self.repo.docs.save(filename=REQUIREMENT_FILENAME, content="")
+ bug_fix = BugFixContext(filename=BUGFIX_FILENAME)
+ return Message(
+ content=bug_fix.model_dump_json(),
+ instruct_content=bug_fix,
+ role="",
+ cause_by=FixBug,
+ sent_from=self,
+ send_to="Alex", # the name of Engineer
+ )
- async def _run_new_requirement(self, requirements, schema=CONFIG.prompt_schema) -> ActionOutput:
- # sas = SearchAndSummarize()
- # # rsp = await sas.run(context=requirements, system_text=SEARCH_AND_SUMMARIZE_SYSTEM_EN_US)
- # rsp = ""
- # info = f"### Search Results\n{sas.result}\n\n### Search Summary\n{rsp}"
- # if sas.result:
- # logger.info(sas.result)
- # logger.info(rsp)
- project_name = CONFIG.project_name or ""
- context = CONTEXT_TEMPLATE.format(requirements=requirements, project_name=project_name)
+ async def _handle_new_requirement(self, req: Document) -> ActionOutput:
+ """handle new requirement"""
+ project_name = self.project_name
+ context = CONTEXT_TEMPLATE.format(requirements=req, project_name=project_name)
exclude = [PROJECT_NAME.key] if project_name else []
node = await WRITE_PRD_NODE.fill(context=context, llm=self.llm, exclude=exclude) # schema=schema
await self._rename_workspace(node)
- return node
+ new_prd_doc = await self.repo.docs.prd.save(
+ filename=FileRepository.new_filename() + ".json", content=node.instruct_content.model_dump_json()
+ )
+ await self._save_competitive_analysis(new_prd_doc)
+ await self.repo.resources.prd.save_pdf(doc=new_prd_doc)
+ return Documents.from_iterable(documents=[new_prd_doc]).to_action_output()
- async def _is_relative(self, new_requirement_doc, old_prd_doc) -> bool:
- context = NEW_REQ_TEMPLATE.format(old_prd=old_prd_doc.content, requirements=new_requirement_doc.content)
+ async def _handle_requirement_update(self, req: Document, related_docs: list[Document]) -> ActionOutput:
+ # ... requirement update logic ...
+ for doc in related_docs:
+ await self._update_prd(req, doc)
+ return Documents.from_iterable(documents=related_docs).to_action_output()
+
+ async def _is_bugfix(self, context: str) -> bool:
+ if not self.repo.code_files_exists():
+ return False
+ node = await WP_ISSUE_TYPE_NODE.fill(context, self.llm)
+ return node.get("issue_type") == "BUG"
+
+ async def get_related_docs(self, req: Document, docs: list[Document]) -> list[Document]:
+ """get the related documents"""
+ # refine: use gather to speed up
+ return [i for i in docs if await self._is_related(req, i)]
+
+ async def _is_related(self, req: Document, old_prd: Document) -> bool:
+ context = NEW_REQ_TEMPLATE.format(old_prd=old_prd.content, requirements=req.content)
node = await WP_IS_RELATIVE_NODE.fill(context, self.llm)
return node.get("is_relative") == "YES"
- async def _merge(self, new_requirement_doc, prd_doc, schema=CONFIG.prompt_schema) -> Document:
- if not CONFIG.project_name:
- CONFIG.project_name = Path(CONFIG.project_path).name
- prompt = NEW_REQ_TEMPLATE.format(requirements=new_requirement_doc.content, old_prd=prd_doc.content)
- node = await WRITE_PRD_NODE.fill(context=prompt, llm=self.llm, schema=schema)
- prd_doc.content = node.instruct_content.model_dump_json()
+ async def _merge(self, req: Document, related_doc: Document) -> Document:
+ if not self.project_name:
+ self.project_name = Path(self.project_path).name
+ prompt = NEW_REQ_TEMPLATE.format(requirements=req.content, old_prd=related_doc.content)
+ node = await REFINED_PRD_NODE.fill(context=prompt, llm=self.llm, schema=self.prompt_schema)
+ related_doc.content = node.instruct_content.model_dump_json()
await self._rename_workspace(node)
- return prd_doc
+ return related_doc
- async def _update_prd(self, requirement_doc, prd_doc, prds_file_repo, *args, **kwargs) -> Document | None:
- if not prd_doc:
- prd = await self._run_new_requirement(
- requirements=[requirement_doc.content if requirement_doc else ""], *args, **kwargs
- )
- new_prd_doc = Document(
- root_path=PRDS_FILE_REPO,
- filename=FileRepository.new_filename() + ".json",
- content=prd.instruct_content.model_dump_json(),
- )
- elif await self._is_relative(requirement_doc, prd_doc):
- new_prd_doc = await self._merge(requirement_doc, prd_doc)
- else:
- return None
- await prds_file_repo.save(filename=new_prd_doc.filename, content=new_prd_doc.content)
+ async def _update_prd(self, req: Document, prd_doc: Document) -> Document:
+ new_prd_doc: Document = await self._merge(req, prd_doc)
+ await self.repo.docs.prd.save_doc(doc=new_prd_doc)
await self._save_competitive_analysis(new_prd_doc)
- await self._save_pdf(new_prd_doc)
+ await self.repo.resources.prd.save_pdf(doc=new_prd_doc)
return new_prd_doc
- @staticmethod
- async def _save_competitive_analysis(prd_doc):
+ async def _save_competitive_analysis(self, prd_doc: Document):
m = json.loads(prd_doc.content)
- quadrant_chart = m.get("Competitive Quadrant Chart")
+ quadrant_chart = m.get(COMPETITIVE_QUADRANT_CHART.key)
if not quadrant_chart:
return
- pathname = (
- CONFIG.git_repo.workdir / Path(COMPETITIVE_ANALYSIS_FILE_REPO) / Path(prd_doc.filename).with_suffix("")
- )
- if not pathname.parent.exists():
- pathname.parent.mkdir(parents=True, exist_ok=True)
- await mermaid_to_file(quadrant_chart, pathname)
+ pathname = self.repo.workdir / COMPETITIVE_ANALYSIS_FILE_REPO / Path(prd_doc.filename).stem
+ pathname.parent.mkdir(parents=True, exist_ok=True)
+ await mermaid_to_file(self.config.mermaid_engine, quadrant_chart, pathname)
- @staticmethod
- async def _save_pdf(prd_doc):
- await FileRepository.save_as(doc=prd_doc, with_suffix=".md", relative_path=PRD_PDF_FILE_REPO)
-
- @staticmethod
- async def _rename_workspace(prd):
- if not CONFIG.project_name:
+ async def _rename_workspace(self, prd):
+ if not self.project_name:
if isinstance(prd, (ActionOutput, ActionNode)):
ws_name = prd.instruct_content.model_dump()["Project Name"]
else:
ws_name = CodeParser.parse_str(block="Project Name", text=prd)
if ws_name:
- CONFIG.project_name = ws_name
- if not CONFIG.project_name: # The LLM failed to provide a project name, and the user didn't provide one either.
- CONFIG.project_name = "app" + uuid.uuid4().hex[:16]
- CONFIG.git_repo.rename_root(CONFIG.project_name)
-
- async def _is_bugfix(self, context) -> bool:
- src_workspace_path = CONFIG.git_repo.workdir / CONFIG.git_repo.workdir.name
- code_files = CONFIG.git_repo.get_files(relative_path=src_workspace_path)
- if not code_files:
- return False
- node = await WP_ISSUE_TYPE_NODE.fill(context, self.llm)
- return node.get("issue_type") == "BUG"
+ self.project_name = ws_name
+ self.repo.git_repo.rename_root(self.project_name)
diff --git a/metagpt/actions/write_prd_an.py b/metagpt/actions/write_prd_an.py
index 948d7d62f..9898be55b 100644
--- a/metagpt/actions/write_prd_an.py
+++ b/metagpt/actions/write_prd_an.py
@@ -8,7 +8,6 @@
from typing import List
from metagpt.actions.action_node import ActionNode
-from metagpt.logs import logger
LANGUAGE = ActionNode(
key="Language",
@@ -31,10 +30,18 @@ ORIGINAL_REQUIREMENTS = ActionNode(
example="Create a 2048 game",
)
+REFINED_REQUIREMENTS = ActionNode(
+ key="Refined Requirements",
+ expected_type=str,
+ instruction="Place the New user's original requirements here.",
+ example="Create a 2048 game with a new feature that ...",
+)
+
PROJECT_NAME = ActionNode(
key="Project Name",
expected_type=str,
- instruction="According to the content of \"Original Requirements,\" name the project using snake case style , like 'game_2048' or 'simple_crm.",
+ instruction='According to the content of "Original Requirements," name the project using snake case style , '
+ "like 'game_2048' or 'simple_crm.",
example="game_2048",
)
@@ -45,6 +52,18 @@ PRODUCT_GOALS = ActionNode(
example=["Create an engaging user experience", "Improve accessibility, be responsive", "More beautiful UI"],
)
+REFINED_PRODUCT_GOALS = ActionNode(
+ key="Refined Product Goals",
+ expected_type=List[str],
+ instruction="Update and expand the original product goals to reflect the evolving needs due to incremental "
+ "development.Ensure that the refined goals align with the current project direction and contribute to its success.",
+ example=[
+ "Enhance user engagement through new features",
+ "Optimize performance for scalability",
+ "Integrate innovative UI enhancements",
+ ],
+)
+
USER_STORIES = ActionNode(
key="User Stories",
expected_type=List[str],
@@ -58,6 +77,20 @@ USER_STORIES = ActionNode(
],
)
+REFINED_USER_STORIES = ActionNode(
+ key="Refined User Stories",
+ expected_type=List[str],
+ instruction="Update and expand the original scenario-based user stories to reflect the evolving needs due to "
+ "incremental development. Ensure that the refined user stories capture incremental features and improvements. ",
+ example=[
+ "As a player, I want to choose difficulty levels to challenge my skills",
+ "As a player, I want a visually appealing score display after each game for a better gaming experience",
+ "As a player, I want a convenient restart button displayed when I lose to quickly start a new game",
+ "As a player, I want an enhanced and aesthetically pleasing UI to elevate the overall gaming experience",
+ "As a player, I want the ability to play the game seamlessly on my mobile phone for on-the-go entertainment",
+ ],
+)
+
COMPETITIVE_ANALYSIS = ActionNode(
key="Competitive Analysis",
expected_type=List[str],
@@ -97,6 +130,15 @@ REQUIREMENT_ANALYSIS = ActionNode(
example="",
)
+REFINED_REQUIREMENT_ANALYSIS = ActionNode(
+ key="Refined Requirement Analysis",
+ expected_type=List[str],
+ instruction="Review and refine the existing requirement analysis to align with the evolving needs of the project "
+ "due to incremental development. Ensure the analysis comprehensively covers the new features and enhancements "
+ "required for the refined project scope.",
+ example=["Require add/update/modify ..."],
+)
+
REQUIREMENT_POOL = ActionNode(
key="Requirement Pool",
expected_type=List[List[str]],
@@ -104,6 +146,14 @@ REQUIREMENT_POOL = ActionNode(
example=[["P0", "The main code ..."], ["P0", "The game algorithm ..."]],
)
+REFINED_REQUIREMENT_POOL = ActionNode(
+ key="Refined Requirement Pool",
+ expected_type=List[List[str]],
+ instruction="List down the top 5 to 7 requirements with their priority (P0, P1, P2). "
+ "Cover both legacy content and incremental content. Retain content unrelated to incremental development",
+ example=[["P0", "The main code ..."], ["P0", "The game algorithm ..."]],
+)
+
UI_DESIGN_DRAFT = ActionNode(
key="UI Design draft",
expected_type=str,
@@ -152,15 +202,22 @@ NODES = [
ANYTHING_UNCLEAR,
]
+REFINED_NODES = [
+ LANGUAGE,
+ PROGRAMMING_LANGUAGE,
+ REFINED_REQUIREMENTS,
+ PROJECT_NAME,
+ REFINED_PRODUCT_GOALS,
+ REFINED_USER_STORIES,
+ COMPETITIVE_ANALYSIS,
+ COMPETITIVE_QUADRANT_CHART,
+ REFINED_REQUIREMENT_ANALYSIS,
+ REFINED_REQUIREMENT_POOL,
+ UI_DESIGN_DRAFT,
+ ANYTHING_UNCLEAR,
+]
+
WRITE_PRD_NODE = ActionNode.from_children("WritePRD", NODES)
+REFINED_PRD_NODE = ActionNode.from_children("RefinedPRD", REFINED_NODES)
WP_ISSUE_TYPE_NODE = ActionNode.from_children("WP_ISSUE_TYPE", [ISSUE_TYPE, REASON])
WP_IS_RELATIVE_NODE = ActionNode.from_children("WP_IS_RELATIVE", [IS_RELATIVE, REASON])
-
-
-def main():
- prompt = WRITE_PRD_NODE.compile(context="")
- logger.info(prompt)
-
-
-if __name__ == "__main__":
- main()
diff --git a/metagpt/actions/write_prd_review.py b/metagpt/actions/write_prd_review.py
index 2babe38db..68fb5d9e8 100644
--- a/metagpt/actions/write_prd_review.py
+++ b/metagpt/actions/write_prd_review.py
@@ -13,7 +13,7 @@ from metagpt.actions.action import Action
class WritePRDReview(Action):
name: str = ""
- context: Optional[str] = None
+ i_context: Optional[str] = None
prd: Optional[str] = None
desc: str = "Based on the PRD, conduct a PRD Review, providing clear and detailed feedback"
diff --git a/metagpt/actions/write_teaching_plan.py b/metagpt/actions/write_teaching_plan.py
index b824e055e..c5f70ae05 100644
--- a/metagpt/actions/write_teaching_plan.py
+++ b/metagpt/actions/write_teaching_plan.py
@@ -8,14 +8,14 @@
from typing import Optional
from metagpt.actions import Action
-from metagpt.config import CONFIG
+from metagpt.context import Context
from metagpt.logs import logger
class WriteTeachingPlanPart(Action):
"""Write Teaching Plan Part"""
- context: Optional[str] = None
+ i_context: Optional[str] = None
topic: str = ""
language: str = "Chinese"
rsp: Optional[str] = None
@@ -24,7 +24,7 @@ class WriteTeachingPlanPart(Action):
statement_patterns = TeachingPlanBlock.TOPIC_STATEMENTS.get(self.topic, [])
statements = []
for p in statement_patterns:
- s = self.format_value(p)
+ s = self.format_value(p, context=self.context)
statements.append(s)
formatter = (
TeachingPlanBlock.PROMPT_TITLE_TEMPLATE
@@ -35,7 +35,7 @@ class WriteTeachingPlanPart(Action):
formation=TeachingPlanBlock.FORMATION,
role=self.prefix,
statements="\n".join(statements),
- lesson=self.context,
+ lesson=self.i_context,
topic=self.topic,
language=self.language,
)
@@ -68,20 +68,23 @@ class WriteTeachingPlanPart(Action):
return self.topic
@staticmethod
- def format_value(value):
+ def format_value(value, context: Context):
"""Fill parameters inside `value` with `options`."""
if not isinstance(value, str):
return value
if "{" not in value:
return value
- merged_opts = CONFIG.options or {}
+ options = context.config.model_dump()
+ for k, v in context.kwargs:
+ options[k] = v # None value is allowed to override and disable the value from config.
+ opts = {k: v for k, v in options.items() if v is not None}
try:
- return value.format(**merged_opts)
+ return value.format(**opts)
except KeyError as e:
logger.warning(f"Parameter is missing:{e}")
- for k, v in merged_opts.items():
+ for k, v in opts.items():
value = value.replace("{" + f"{k}" + "}", str(v))
return value
diff --git a/metagpt/actions/write_test.py b/metagpt/actions/write_test.py
index 96486311f..978fa20a6 100644
--- a/metagpt/actions/write_test.py
+++ b/metagpt/actions/write_test.py
@@ -39,7 +39,7 @@ you should correctly import the necessary classes based on these file locations!
class WriteTest(Action):
name: str = "WriteTest"
- context: Optional[TestingContext] = None
+ i_context: Optional[TestingContext] = None
async def write_code(self, prompt):
code_rsp = await self._aask(prompt)
@@ -55,16 +55,16 @@ class WriteTest(Action):
return code
async def run(self, *args, **kwargs) -> TestingContext:
- if not self.context.test_doc:
- self.context.test_doc = Document(
- filename="test_" + self.context.code_doc.filename, root_path=TEST_CODES_FILE_REPO
+ if not self.i_context.test_doc:
+ self.i_context.test_doc = Document(
+ filename="test_" + self.i_context.code_doc.filename, root_path=TEST_CODES_FILE_REPO
)
fake_root = "/data"
prompt = PROMPT_TEMPLATE.format(
- code_to_test=self.context.code_doc.content,
- test_file_name=self.context.test_doc.filename,
- source_file_path=fake_root + "/" + self.context.code_doc.root_relative_path,
+ code_to_test=self.i_context.code_doc.content,
+ test_file_name=self.i_context.test_doc.filename,
+ source_file_path=fake_root + "/" + self.i_context.code_doc.root_relative_path,
workspace=fake_root,
)
- self.context.test_doc.content = await self.write_code(prompt)
- return self.context
+ self.i_context.test_doc.content = await self.write_code(prompt)
+ return self.i_context
diff --git a/metagpt/config.py b/metagpt/config.py
deleted file mode 100644
index 8cfbbe973..000000000
--- a/metagpt/config.py
+++ /dev/null
@@ -1,290 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-"""
-Provide configuration, singleton
-@Modified By: mashenquan, 2023/11/27.
- 1. According to Section 2.2.3.11 of RFC 135, add git repository support.
- 2. Add the parameter `src_workspace` for the old version project path.
-"""
-import datetime
-import json
-import os
-import warnings
-from copy import deepcopy
-from enum import Enum
-from pathlib import Path
-from typing import Any
-from uuid import uuid4
-
-import yaml
-
-from metagpt.const import DEFAULT_WORKSPACE_ROOT, METAGPT_ROOT, OPTIONS
-from metagpt.logs import logger
-from metagpt.tools import SearchEngineType, WebBrowserEngineType
-from metagpt.utils.common import require_python_version
-from metagpt.utils.cost_manager import CostManager
-from metagpt.utils.singleton import Singleton
-
-
-class NotConfiguredException(Exception):
- """Exception raised for errors in the configuration.
-
- Attributes:
- message -- explanation of the error
- """
-
- def __init__(self, message="The required configuration is not set"):
- self.message = message
- super().__init__(self.message)
-
-
-class LLMProviderEnum(Enum):
- OPENAI = "openai"
- ANTHROPIC = "anthropic"
- SPARK = "spark"
- ZHIPUAI = "zhipuai"
- FIREWORKS = "fireworks"
- OPEN_LLM = "open_llm"
- GEMINI = "gemini"
- METAGPT = "metagpt"
- AZURE_OPENAI = "azure_openai"
- OLLAMA = "ollama"
-
- def __missing__(self, key):
- return self.OPENAI
-
-
-class Config(metaclass=Singleton):
- """
- Regular usage method:
- config = Config("config.yaml")
- secret_key = config.get_key("MY_SECRET_KEY")
- print("Secret key:", secret_key)
- """
-
- _instance = None
- home_yaml_file = Path.home() / ".metagpt/config.yaml"
- key_yaml_file = METAGPT_ROOT / "config/key.yaml"
- default_yaml_file = METAGPT_ROOT / "config/config.yaml"
-
- def __init__(self, yaml_file=default_yaml_file, cost_data=""):
- global_options = OPTIONS.get()
- # cli paras
- self.project_path = ""
- self.project_name = ""
- self.inc = False
- self.reqa_file = ""
- self.max_auto_summarize_code = 0
- self.git_reinit = False
-
- self._init_with_config_files_and_env(yaml_file)
- # The agent needs to be billed per user, so billing information cannot be destroyed when the session ends.
- self.cost_manager = CostManager(**json.loads(cost_data)) if cost_data else CostManager()
- self._update()
- global_options.update(OPTIONS.get())
- logger.debug("Config loading done.")
-
- def get_default_llm_provider_enum(self) -> LLMProviderEnum:
- """Get first valid LLM provider enum"""
- mappings = {
- LLMProviderEnum.OPENAI: bool(
- self._is_valid_llm_key(self.OPENAI_API_KEY) and not self.OPENAI_API_TYPE and self.OPENAI_API_MODEL
- ),
- LLMProviderEnum.ANTHROPIC: self._is_valid_llm_key(self.ANTHROPIC_API_KEY),
- LLMProviderEnum.ZHIPUAI: self._is_valid_llm_key(self.ZHIPUAI_API_KEY),
- LLMProviderEnum.FIREWORKS: self._is_valid_llm_key(self.FIREWORKS_API_KEY),
- LLMProviderEnum.OPEN_LLM: self._is_valid_llm_key(self.OPEN_LLM_API_BASE),
- LLMProviderEnum.GEMINI: self._is_valid_llm_key(self.GEMINI_API_KEY),
- LLMProviderEnum.METAGPT: bool(
- self._is_valid_llm_key(self.OPENAI_API_KEY) and self.OPENAI_API_TYPE == "metagpt"
- ),
- LLMProviderEnum.AZURE_OPENAI: bool(
- self._is_valid_llm_key(self.OPENAI_API_KEY)
- and self.OPENAI_API_TYPE == "azure"
- and self.DEPLOYMENT_NAME
- and self.OPENAI_API_VERSION
- ),
- LLMProviderEnum.OLLAMA: self._is_valid_llm_key(self.OLLAMA_API_BASE),
- }
- provider = None
- for k, v in mappings.items():
- if v:
- provider = k
- break
- if provider is None:
- if self.DEFAULT_PROVIDER:
- provider = LLMProviderEnum(self.DEFAULT_PROVIDER)
- else:
- raise NotConfiguredException("You should config a LLM configuration first")
-
- if provider is LLMProviderEnum.GEMINI and not require_python_version(req_version=(3, 10)):
- warnings.warn("Use Gemini requires Python >= 3.10")
- model_name = self.get_model_name(provider=provider)
- if model_name:
- logger.info(f"{provider} Model: {model_name}")
- if provider:
- logger.info(f"API: {provider}")
- return provider
-
- def get_model_name(self, provider=None) -> str:
- provider = provider or self.get_default_llm_provider_enum()
- model_mappings = {
- LLMProviderEnum.OPENAI: self.OPENAI_API_MODEL,
- LLMProviderEnum.AZURE_OPENAI: self.DEPLOYMENT_NAME,
- }
- return model_mappings.get(provider, "")
-
- @staticmethod
- def _is_valid_llm_key(k: str) -> bool:
- return bool(k and k != "YOUR_API_KEY")
-
- def _update(self):
- self.global_proxy = self._get("GLOBAL_PROXY")
-
- self.openai_api_key = self._get("OPENAI_API_KEY")
- self.anthropic_api_key = self._get("ANTHROPIC_API_KEY")
- self.zhipuai_api_key = self._get("ZHIPUAI_API_KEY")
- self.open_llm_api_base = self._get("OPEN_LLM_API_BASE")
- self.open_llm_api_model = self._get("OPEN_LLM_API_MODEL")
- self.fireworks_api_key = self._get("FIREWORKS_API_KEY")
- self.gemini_api_key = self._get("GEMINI_API_KEY")
- self.ollama_api_base = self._get("OLLAMA_API_BASE")
- self.ollama_api_model = self._get("OLLAMA_API_MODEL")
-
- if not self._get("DISABLE_LLM_PROVIDER_CHECK"):
- _ = self.get_default_llm_provider_enum()
-
- self.openai_base_url = self._get("OPENAI_BASE_URL")
- self.openai_proxy = self._get("OPENAI_PROXY") or self.global_proxy
- self.openai_api_type = self._get("OPENAI_API_TYPE")
- self.openai_api_version = self._get("OPENAI_API_VERSION")
- self.openai_api_rpm = self._get("RPM", 3)
- self.openai_api_model = self._get("OPENAI_API_MODEL", "gpt-4-1106-preview")
- self.max_tokens_rsp = self._get("MAX_TOKENS", 2048)
- self.deployment_name = self._get("DEPLOYMENT_NAME", "gpt-4")
-
- self.spark_appid = self._get("SPARK_APPID")
- self.spark_api_secret = self._get("SPARK_API_SECRET")
- self.spark_api_key = self._get("SPARK_API_KEY")
- self.domain = self._get("DOMAIN")
- self.spark_url = self._get("SPARK_URL")
-
- self.fireworks_api_base = self._get("FIREWORKS_API_BASE")
- self.fireworks_api_model = self._get("FIREWORKS_API_MODEL")
-
- self.claude_api_key = self._get("ANTHROPIC_API_KEY")
- self.serpapi_api_key = self._get("SERPAPI_API_KEY")
- self.serper_api_key = self._get("SERPER_API_KEY")
- self.google_api_key = self._get("GOOGLE_API_KEY")
- self.google_cse_id = self._get("GOOGLE_CSE_ID")
- self.search_engine = SearchEngineType(self._get("SEARCH_ENGINE", SearchEngineType.SERPAPI_GOOGLE))
- self.web_browser_engine = WebBrowserEngineType(self._get("WEB_BROWSER_ENGINE", WebBrowserEngineType.PLAYWRIGHT))
- self.playwright_browser_type = self._get("PLAYWRIGHT_BROWSER_TYPE", "chromium")
- self.selenium_browser_type = self._get("SELENIUM_BROWSER_TYPE", "chrome")
-
- self.long_term_memory = self._get("LONG_TERM_MEMORY", False)
- if self.long_term_memory:
- logger.warning("LONG_TERM_MEMORY is True")
- self.cost_manager.max_budget = self._get("MAX_BUDGET", 10.0)
- self.code_review_k_times = 2
-
- self.puppeteer_config = self._get("PUPPETEER_CONFIG", "")
- self.mmdc = self._get("MMDC", "mmdc")
- self.calc_usage = self._get("CALC_USAGE", True)
- self.model_for_researcher_summary = self._get("MODEL_FOR_RESEARCHER_SUMMARY")
- self.model_for_researcher_report = self._get("MODEL_FOR_RESEARCHER_REPORT")
- self.mermaid_engine = self._get("MERMAID_ENGINE", "nodejs")
- self.pyppeteer_executable_path = self._get("PYPPETEER_EXECUTABLE_PATH", "")
-
- workspace_uid = (
- self._get("WORKSPACE_UID") or f"{datetime.datetime.now().strftime('%Y%m%d%H%M%S')}-{uuid4().hex[-8:]}"
- )
- self.repair_llm_output = self._get("REPAIR_LLM_OUTPUT", False)
- self.prompt_schema = self._get("PROMPT_FORMAT", "json")
- self.workspace_path = Path(self._get("WORKSPACE_PATH", DEFAULT_WORKSPACE_ROOT))
- val = self._get("WORKSPACE_PATH_WITH_UID")
- if val and val.lower() == "true": # for agent
- self.workspace_path = self.workspace_path / workspace_uid
- self._ensure_workspace_exists()
- self.max_auto_summarize_code = self.max_auto_summarize_code or self._get("MAX_AUTO_SUMMARIZE_CODE", 1)
- self.timeout = int(self._get("TIMEOUT", 3))
-
- self.kaggle_username = self._get("KAGGLE_USERNAME", "")
- self.kaggle_key = self._get("KAGGLE_KEY", "")
-
- def update_via_cli(self, project_path, project_name, inc, reqa_file, max_auto_summarize_code):
- """update config via cli"""
-
- # Use in the PrepareDocuments action according to Section 2.2.3.5.1 of RFC 135.
- if project_path:
- inc = True
- project_name = project_name or Path(project_path).name
- self.project_path = project_path
- self.project_name = project_name
- self.inc = inc
- self.reqa_file = reqa_file
- self.max_auto_summarize_code = max_auto_summarize_code
-
- def _ensure_workspace_exists(self):
- self.workspace_path.mkdir(parents=True, exist_ok=True)
- logger.debug(f"WORKSPACE_PATH set to {self.workspace_path}")
-
- def _init_with_config_files_and_env(self, yaml_file):
- """Load from config/key.yaml, config/config.yaml, and env in decreasing order of priority"""
- configs = dict(os.environ)
-
- for _yaml_file in [yaml_file, self.key_yaml_file, self.home_yaml_file]:
- if not _yaml_file.exists():
- continue
-
- # Load local YAML file
- with open(_yaml_file, "r", encoding="utf-8") as file:
- yaml_data = yaml.safe_load(file)
- if not yaml_data:
- continue
- configs.update(yaml_data)
- OPTIONS.set(configs)
-
- @staticmethod
- def _get(*args, **kwargs):
- i = OPTIONS.get()
- return i.get(*args, **kwargs)
-
- def get(self, key, *args, **kwargs):
- """Retrieve values from config/key.yaml, config/config.yaml, and environment variables.
- Throw an error if not found."""
- value = self._get(key, *args, **kwargs)
- if value is None:
- raise ValueError(f"Key '{key}' not found in environment variables or in the YAML file")
- return value
-
- def __setattr__(self, name: str, value: Any) -> None:
- OPTIONS.get()[name] = value
-
- def __getattr__(self, name: str) -> Any:
- i = OPTIONS.get()
- return i.get(name)
-
- def set_context(self, options: dict):
- """Update current config"""
- if not options:
- return
- opts = deepcopy(OPTIONS.get())
- opts.update(options)
- OPTIONS.set(opts)
- self._update()
-
- @property
- def options(self):
- """Return all key-values"""
- return OPTIONS.get()
-
- def new_environ(self):
- """Return a new os.environ object"""
- env = os.environ.copy()
- i = self.options
- env.update({k: v for k, v in i.items() if isinstance(v, str)})
- return env
-
-
-CONFIG = Config()
diff --git a/metagpt/config2.py b/metagpt/config2.py
new file mode 100644
index 000000000..dc53ee661
--- /dev/null
+++ b/metagpt/config2.py
@@ -0,0 +1,145 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+"""
+@Time : 2024/1/4 01:25
+@Author : alexanderwu
+@File : config2.py
+"""
+import os
+from pathlib import Path
+from typing import Dict, Iterable, List, Literal, Optional
+
+from pydantic import BaseModel, model_validator
+
+from metagpt.configs.browser_config import BrowserConfig
+from metagpt.configs.llm_config import LLMConfig, LLMType
+from metagpt.configs.mermaid_config import MermaidConfig
+from metagpt.configs.redis_config import RedisConfig
+from metagpt.configs.s3_config import S3Config
+from metagpt.configs.search_config import SearchConfig
+from metagpt.configs.workspace_config import WorkspaceConfig
+from metagpt.const import CONFIG_ROOT, METAGPT_ROOT
+from metagpt.utils.yaml_model import YamlModel
+
+
+class CLIParams(BaseModel):
+ """CLI parameters"""
+
+ project_path: str = ""
+ project_name: str = ""
+ inc: bool = False
+ reqa_file: str = ""
+ max_auto_summarize_code: int = 0
+ git_reinit: bool = False
+
+ @model_validator(mode="after")
+ def check_project_path(self):
+ """Check project_path and project_name"""
+ if self.project_path:
+ self.inc = True
+ self.project_name = self.project_name or Path(self.project_path).name
+ return self
+
+
+class Config(CLIParams, YamlModel):
+ """Configurations for MetaGPT"""
+
+ # Key Parameters
+ llm: LLMConfig
+
+ # Global Proxy. Will be used if llm.proxy is not set
+ proxy: str = ""
+
+ # Tool Parameters
+ search: Optional[SearchConfig] = None
+ browser: BrowserConfig = BrowserConfig()
+ mermaid: MermaidConfig = MermaidConfig()
+
+ # Storage Parameters
+ s3: Optional[S3Config] = None
+ redis: Optional[RedisConfig] = None
+
+ # Misc Parameters
+ repair_llm_output: bool = False
+ prompt_schema: Literal["json", "markdown", "raw"] = "json"
+ workspace: WorkspaceConfig = WorkspaceConfig()
+ enable_longterm_memory: bool = False
+ code_review_k_times: int = 2
+
+ # Will be removed in the future
+ llm_for_researcher_summary: str = "gpt3"
+ llm_for_researcher_report: str = "gpt3"
+ METAGPT_TEXT_TO_IMAGE_MODEL_URL: str = ""
+ language: str = "English"
+ redis_key: str = "placeholder"
+ mmdc: str = "mmdc"
+ puppeteer_config: str = ""
+ pyppeteer_executable_path: str = ""
+ IFLYTEK_APP_ID: str = ""
+ IFLYTEK_API_SECRET: str = ""
+ IFLYTEK_API_KEY: str = ""
+ AZURE_TTS_SUBSCRIPTION_KEY: str = ""
+ AZURE_TTS_REGION: str = ""
+ mermaid_engine: str = "nodejs"
+ OPENAI_VISION_MODEL: str = "gpt-4-vision-preview"
+ VISION_MAX_TOKENS: int = 4096
+
+ @classmethod
+ def from_home(cls, path):
+ """Load config from ~/.metagpt/config.yaml"""
+ pathname = CONFIG_ROOT / path
+ if not pathname.exists():
+ return None
+ return Config.from_yaml_file(pathname)
+
+ @classmethod
+ def default(cls):
+ """Load default config
+ - Priority: env < default_config_paths
+ - Inside default_config_paths, the latter one overwrites the former one
+ """
+ default_config_paths: List[Path] = [
+ METAGPT_ROOT / "config/config2.yaml",
+ Path.home() / ".metagpt/config2.yaml",
+ ]
+
+ dicts = [dict(os.environ)]
+ dicts += [Config.read_yaml(path) for path in default_config_paths]
+ final = merge_dict(dicts)
+ return Config(**final)
+
+ def update_via_cli(self, project_path, project_name, inc, reqa_file, max_auto_summarize_code):
+ """update config via cli"""
+
+ # Use in the PrepareDocuments action according to Section 2.2.3.5.1 of RFC 135.
+ if project_path:
+ inc = True
+ project_name = project_name or Path(project_path).name
+ self.project_path = project_path
+ self.project_name = project_name
+ self.inc = inc
+ self.reqa_file = reqa_file
+ self.max_auto_summarize_code = max_auto_summarize_code
+
+ def get_openai_llm(self) -> Optional[LLMConfig]:
+ """Get OpenAI LLMConfig by name. If no OpenAI, raise Exception"""
+ if self.llm.api_type == LLMType.OPENAI:
+ return self.llm
+ return None
+
+ def get_azure_llm(self) -> Optional[LLMConfig]:
+ """Get Azure LLMConfig by name. If no Azure, raise Exception"""
+ if self.llm.api_type == LLMType.AZURE:
+ return self.llm
+ return None
+
+
+def merge_dict(dicts: Iterable[Dict]) -> Dict:
+ """Merge multiple dicts into one, with the latter dict overwriting the former"""
+ result = {}
+ for dictionary in dicts:
+ result.update(dictionary)
+ return result
+
+
+config = Config.default()
diff --git a/tests/metagpt/test_action.py b/metagpt/configs/__init__.py
similarity index 59%
rename from tests/metagpt/test_action.py
rename to metagpt/configs/__init__.py
index af5106ab4..e42e6788f 100644
--- a/tests/metagpt/test_action.py
+++ b/metagpt/configs/__init__.py
@@ -1,7 +1,7 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
-@Time : 2023/5/11 14:44
+@Time : 2024/1/4 16:33
@Author : alexanderwu
-@File : test_action.py
+@File : __init__.py
"""
diff --git a/metagpt/configs/browser_config.py b/metagpt/configs/browser_config.py
new file mode 100644
index 000000000..00f918735
--- /dev/null
+++ b/metagpt/configs/browser_config.py
@@ -0,0 +1,20 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+"""
+@Time : 2024/1/4 19:06
+@Author : alexanderwu
+@File : browser_config.py
+"""
+from typing import Literal
+
+from metagpt.tools import WebBrowserEngineType
+from metagpt.utils.yaml_model import YamlModel
+
+
+class BrowserConfig(YamlModel):
+ """Config for Browser"""
+
+ engine: WebBrowserEngineType = WebBrowserEngineType.PLAYWRIGHT
+ browser: Literal["chrome", "firefox", "edge", "ie"] = "chrome"
+ driver: Literal["chromium", "firefox", "webkit"] = "chromium"
+ path: str = ""
diff --git a/metagpt/configs/llm_config.py b/metagpt/configs/llm_config.py
new file mode 100644
index 000000000..626d4242f
--- /dev/null
+++ b/metagpt/configs/llm_config.py
@@ -0,0 +1,78 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+"""
+@Time : 2024/1/4 16:33
+@Author : alexanderwu
+@File : llm_config.py
+"""
+from enum import Enum
+from typing import Optional
+
+from pydantic import field_validator
+
+from metagpt.utils.yaml_model import YamlModel
+
+
+class LLMType(Enum):
+ OPENAI = "openai"
+ ANTHROPIC = "anthropic"
+ SPARK = "spark"
+ ZHIPUAI = "zhipuai"
+ FIREWORKS = "fireworks"
+ OPEN_LLM = "open_llm"
+ GEMINI = "gemini"
+ METAGPT = "metagpt"
+ AZURE = "azure"
+ OLLAMA = "ollama"
+
+ def __missing__(self, key):
+ return self.OPENAI
+
+
+class LLMConfig(YamlModel):
+ """Config for LLM
+
+ OpenAI: https://github.com/openai/openai-python/blob/main/src/openai/resources/chat/completions.py#L681
+ Optional Fields in pydantic: https://docs.pydantic.dev/latest/migration/#required-optional-and-nullable-fields
+ """
+
+ api_key: str
+ api_type: LLMType = LLMType.OPENAI
+ base_url: str = "https://api.openai.com/v1"
+ api_version: Optional[str] = None
+
+ model: Optional[str] = None # also stands for DEPLOYMENT_NAME
+
+ # For Spark(Xunfei), maybe remove later
+ app_id: Optional[str] = None
+ api_secret: Optional[str] = None
+ domain: Optional[str] = None
+
+ # For Chat Completion
+ max_token: int = 4096
+ temperature: float = 0.0
+ top_p: float = 1.0
+ top_k: int = 0
+ repetition_penalty: float = 1.0
+ stop: Optional[str] = None
+ presence_penalty: float = 0.0
+ frequency_penalty: float = 0.0
+ best_of: Optional[int] = None
+ n: Optional[int] = None
+ stream: bool = False
+ logprobs: Optional[bool] = None # https://cookbook.openai.com/examples/using_logprobs
+ top_logprobs: Optional[int] = None
+ timeout: int = 60
+
+ # For Network
+ proxy: Optional[str] = None
+
+ # Cost Control
+ calc_usage: bool = True
+
+ @field_validator("api_key")
+ @classmethod
+ def check_llm_key(cls, v):
+ if v in ["", None, "YOUR_API_KEY"]:
+ raise ValueError("Please set your API key in config.yaml")
+ return v
diff --git a/metagpt/configs/mermaid_config.py b/metagpt/configs/mermaid_config.py
new file mode 100644
index 000000000..de4a3865c
--- /dev/null
+++ b/metagpt/configs/mermaid_config.py
@@ -0,0 +1,18 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+"""
+@Time : 2024/1/4 19:07
+@Author : alexanderwu
+@File : mermaid_config.py
+"""
+from typing import Literal
+
+from metagpt.utils.yaml_model import YamlModel
+
+
+class MermaidConfig(YamlModel):
+ """Config for Mermaid"""
+
+ engine: Literal["nodejs", "ink", "playwright", "pyppeteer"] = "nodejs"
+ path: str = ""
+ puppeteer_config: str = "" # Only for nodejs engine
diff --git a/metagpt/configs/redis_config.py b/metagpt/configs/redis_config.py
new file mode 100644
index 000000000..c4cfb6764
--- /dev/null
+++ b/metagpt/configs/redis_config.py
@@ -0,0 +1,26 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+"""
+@Time : 2024/1/4 19:06
+@Author : alexanderwu
+@File : redis_config.py
+"""
+from metagpt.utils.yaml_model import YamlModelWithoutDefault
+
+
+class RedisConfig(YamlModelWithoutDefault):
+ host: str
+ port: int
+ username: str = ""
+ password: str
+ db: str
+
+ def to_url(self):
+ return f"redis://{self.host}:{self.port}"
+
+ def to_kwargs(self):
+ return {
+ "username": self.username,
+ "password": self.password,
+ "db": self.db,
+ }
diff --git a/metagpt/configs/s3_config.py b/metagpt/configs/s3_config.py
new file mode 100644
index 000000000..72b81fae4
--- /dev/null
+++ b/metagpt/configs/s3_config.py
@@ -0,0 +1,15 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+"""
+@Time : 2024/1/4 19:07
+@Author : alexanderwu
+@File : s3_config.py
+"""
+from metagpt.utils.yaml_model import YamlModelWithoutDefault
+
+
+class S3Config(YamlModelWithoutDefault):
+ access_key: str
+ secret_key: str
+ endpoint: str
+ bucket: str
diff --git a/metagpt/configs/search_config.py b/metagpt/configs/search_config.py
new file mode 100644
index 000000000..a8ae918db
--- /dev/null
+++ b/metagpt/configs/search_config.py
@@ -0,0 +1,17 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+"""
+@Time : 2024/1/4 19:06
+@Author : alexanderwu
+@File : search_config.py
+"""
+from metagpt.tools import SearchEngineType
+from metagpt.utils.yaml_model import YamlModel
+
+
+class SearchConfig(YamlModel):
+ """Config for Search"""
+
+ api_key: str
+ api_type: SearchEngineType = SearchEngineType.SERPAPI_GOOGLE
+ cse_id: str = "" # for google
diff --git a/metagpt/configs/workspace_config.py b/metagpt/configs/workspace_config.py
new file mode 100644
index 000000000..df7aeaef9
--- /dev/null
+++ b/metagpt/configs/workspace_config.py
@@ -0,0 +1,38 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+"""
+@Time : 2024/1/4 19:09
+@Author : alexanderwu
+@File : workspace_config.py
+"""
+from datetime import datetime
+from pathlib import Path
+from uuid import uuid4
+
+from pydantic import field_validator, model_validator
+
+from metagpt.const import DEFAULT_WORKSPACE_ROOT
+from metagpt.utils.yaml_model import YamlModel
+
+
+class WorkspaceConfig(YamlModel):
+ path: Path = DEFAULT_WORKSPACE_ROOT
+ use_uid: bool = False
+ uid: str = ""
+
+ @field_validator("path")
+ @classmethod
+ def check_workspace_path(cls, v):
+ if isinstance(v, str):
+ v = Path(v)
+ return v
+
+ @model_validator(mode="after")
+ def check_uid_and_update_path(self):
+ if self.use_uid and not self.uid:
+ self.uid = f"{datetime.now().strftime('%Y%m%d%H%M%S')}-{uuid4().hex[-8:]}"
+ self.path = self.path / self.uid
+
+ # Create workspace path if not exists
+ self.path.mkdir(parents=True, exist_ok=True)
+ return self
diff --git a/metagpt/const.py b/metagpt/const.py
index 7a19e81d0..2cffaa804 100644
--- a/metagpt/const.py
+++ b/metagpt/const.py
@@ -9,7 +9,6 @@
@Modified By: mashenquan, 2023-11-27. Defines file repository paths according to Section 2.2.3.4 of RFC 135.
@Modified By: mashenquan, 2023/12/5. Add directories for code summarization..
"""
-import contextvars
import os
from pathlib import Path
@@ -17,8 +16,6 @@ from loguru import logger
import metagpt
-OPTIONS = contextvars.ContextVar("OPTIONS", default={})
-
def get_metagpt_package_root():
"""Get the root directory of the installed package."""
@@ -47,7 +44,7 @@ def get_metagpt_root():
# METAGPT PROJECT ROOT AND VARS
-
+CONFIG_ROOT = Path.home() / ".metagpt"
METAGPT_ROOT = get_metagpt_root() # Dependent on METAGPT_PROJECT_ROOT
DEFAULT_WORKSPACE_ROOT = METAGPT_ROOT / "workspace"
@@ -73,12 +70,10 @@ SKILL_DIRECTORY = SOURCE_ROOT / "skills"
TOOL_SCHEMA_PATH = METAGPT_ROOT / "metagpt/tools/schemas"
TOOL_LIBS_PATH = METAGPT_ROOT / "metagpt/tools/libs"
-
# REAL CONSTS
MEM_TTL = 24 * 30 * 3600
-
MESSAGE_ROUTE_FROM = "sent_from"
MESSAGE_ROUTE_TO = "send_to"
MESSAGE_ROUTE_CAUSE_BY = "cause_by"
@@ -89,25 +84,28 @@ MESSAGE_ROUTE_TO_NONE = ""
REQUIREMENT_FILENAME = "requirement.txt"
BUGFIX_FILENAME = "bugfix.txt"
PACKAGE_REQUIREMENTS_FILENAME = "requirements.txt"
+CODE_PLAN_AND_CHANGE_FILENAME = "code_plan_and_change.json"
DOCS_FILE_REPO = "docs"
-PRDS_FILE_REPO = "docs/prds"
+PRDS_FILE_REPO = "docs/prd"
SYSTEM_DESIGN_FILE_REPO = "docs/system_design"
-TASK_FILE_REPO = "docs/tasks"
+TASK_FILE_REPO = "docs/task"
+CODE_PLAN_AND_CHANGE_FILE_REPO = "docs/code_plan_and_change"
COMPETITIVE_ANALYSIS_FILE_REPO = "resources/competitive_analysis"
DATA_API_DESIGN_FILE_REPO = "resources/data_api_design"
SEQ_FLOW_FILE_REPO = "resources/seq_flow"
SYSTEM_DESIGN_PDF_FILE_REPO = "resources/system_design"
PRD_PDF_FILE_REPO = "resources/prd"
-TASK_PDF_FILE_REPO = "resources/api_spec_and_tasks"
+TASK_PDF_FILE_REPO = "resources/api_spec_and_task"
+CODE_PLAN_AND_CHANGE_PDF_FILE_REPO = "resources/code_plan_and_change"
TEST_CODES_FILE_REPO = "tests"
TEST_OUTPUTS_FILE_REPO = "test_outputs"
-CODE_SUMMARIES_FILE_REPO = "docs/code_summaries"
-CODE_SUMMARIES_PDF_FILE_REPO = "resources/code_summaries"
+CODE_SUMMARIES_FILE_REPO = "docs/code_summary"
+CODE_SUMMARIES_PDF_FILE_REPO = "resources/code_summary"
RESOURCES_FILE_REPO = "resources"
-SD_OUTPUT_FILE_REPO = "resources/SD_Output"
+SD_OUTPUT_FILE_REPO = "resources/sd_output"
GRAPH_REPO_FILE_REPO = "docs/graph_repo"
-CLASS_VIEW_FILE_REPO = "docs/class_views"
+CLASS_VIEW_FILE_REPO = "docs/class_view"
YAPI_URL = "http://yapi.deepwisdomai.com/"
diff --git a/metagpt/context.py b/metagpt/context.py
new file mode 100644
index 000000000..3dfd52d58
--- /dev/null
+++ b/metagpt/context.py
@@ -0,0 +1,97 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+"""
+@Time : 2024/1/4 16:32
+@Author : alexanderwu
+@File : context.py
+"""
+import os
+from pathlib import Path
+from typing import Any, Optional
+
+from pydantic import BaseModel, ConfigDict
+
+from metagpt.config2 import Config
+from metagpt.configs.llm_config import LLMConfig
+from metagpt.provider.base_llm import BaseLLM
+from metagpt.provider.llm_provider_registry import create_llm_instance
+from metagpt.utils.cost_manager import CostManager
+from metagpt.utils.git_repository import GitRepository
+from metagpt.utils.project_repo import ProjectRepo
+
+
+class AttrDict(BaseModel):
+ """A dict-like object that allows access to keys as attributes, compatible with Pydantic."""
+
+ model_config = ConfigDict(extra="allow")
+
+ def __init__(self, **kwargs):
+ super().__init__(**kwargs)
+ self.__dict__.update(kwargs)
+
+ def __getattr__(self, key):
+ return self.__dict__.get(key, None)
+
+ def __setattr__(self, key, value):
+ self.__dict__[key] = value
+
+ def __delattr__(self, key):
+ if key in self.__dict__:
+ del self.__dict__[key]
+ else:
+ raise AttributeError(f"No such attribute: {key}")
+
+ def set(self, key, val: Any):
+ self.__dict__[key] = val
+
+ def get(self, key, default: Any = None):
+ return self.__dict__.get(key, default)
+
+ def remove(self, key):
+ if key in self.__dict__:
+ self.__delattr__(key)
+
+
+class Context(BaseModel):
+ """Env context for MetaGPT"""
+
+ model_config = ConfigDict(arbitrary_types_allowed=True)
+
+ kwargs: AttrDict = AttrDict()
+ config: Config = Config.default()
+
+ repo: Optional[ProjectRepo] = None
+ git_repo: Optional[GitRepository] = None
+ src_workspace: Optional[Path] = None
+ cost_manager: CostManager = CostManager()
+
+ _llm: Optional[BaseLLM] = None
+
+ def new_environ(self):
+ """Return a new os.environ object"""
+ env = os.environ.copy()
+ # i = self.options
+ # env.update({k: v for k, v in i.items() if isinstance(v, str)})
+ return env
+
+ # def use_llm(self, name: Optional[str] = None, provider: LLMType = LLMType.OPENAI) -> BaseLLM:
+ # """Use a LLM instance"""
+ # self._llm_config = self.config.get_llm_config(name, provider)
+ # self._llm = None
+ # return self._llm
+
+ def llm(self) -> BaseLLM:
+ """Return a LLM instance, fixme: support cache"""
+ # if self._llm is None:
+ self._llm = create_llm_instance(self.config.llm)
+ if self._llm.cost_manager is None:
+ self._llm.cost_manager = self.cost_manager
+ return self._llm
+
+ def llm_with_cost_manager_from_llm_config(self, llm_config: LLMConfig) -> BaseLLM:
+ """Return a LLM instance, fixme: support cache"""
+ # if self._llm is None:
+ llm = create_llm_instance(llm_config)
+ if llm.cost_manager is None:
+ llm.cost_manager = self.cost_manager
+ return llm
diff --git a/metagpt/context_mixin.py b/metagpt/context_mixin.py
new file mode 100644
index 000000000..bdf2d0734
--- /dev/null
+++ b/metagpt/context_mixin.py
@@ -0,0 +1,102 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+"""
+@Time : 2024/1/11 17:25
+@Author : alexanderwu
+@File : context_mixin.py
+"""
+from typing import Optional
+
+from pydantic import BaseModel, ConfigDict, Field
+
+from metagpt.config2 import Config
+from metagpt.context import Context
+from metagpt.provider.base_llm import BaseLLM
+
+
+class ContextMixin(BaseModel):
+ """Mixin class for context and config"""
+
+ model_config = ConfigDict(arbitrary_types_allowed=True)
+
+ # Pydantic has bug on _private_attr when using inheritance, so we use private_* instead
+ # - https://github.com/pydantic/pydantic/issues/7142
+ # - https://github.com/pydantic/pydantic/issues/7083
+ # - https://github.com/pydantic/pydantic/issues/7091
+
+ # Env/Role/Action will use this context as private context, or use self.context as public context
+ private_context: Optional[Context] = Field(default=None, exclude=True)
+ # Env/Role/Action will use this config as private config, or use self.context.config as public config
+ private_config: Optional[Config] = Field(default=None, exclude=True)
+
+ # Env/Role/Action will use this llm as private llm, or use self.context._llm instance
+ private_llm: Optional[BaseLLM] = Field(default=None, exclude=True)
+
+ def __init__(
+ self,
+ context: Optional[Context] = None,
+ config: Optional[Config] = None,
+ llm: Optional[BaseLLM] = None,
+ **kwargs,
+ ):
+ """Initialize with config"""
+ super().__init__(**kwargs)
+ self.set_context(context)
+ self.set_config(config)
+ self.set_llm(llm)
+
+ def set(self, k, v, override=False):
+ """Set attribute"""
+ if override or not self.__dict__.get(k):
+ self.__dict__[k] = v
+
+ def set_context(self, context: Context, override=True):
+ """Set context"""
+ self.set("private_context", context, override)
+
+ def set_config(self, config: Config, override=False):
+ """Set config"""
+ self.set("private_config", config, override)
+ if config is not None:
+ _ = self.llm # init llm
+
+ def set_llm(self, llm: BaseLLM, override=False):
+ """Set llm"""
+ self.set("private_llm", llm, override)
+
+ @property
+ def config(self) -> Config:
+ """Role config: role config > context config"""
+ if self.private_config:
+ return self.private_config
+ return self.context.config
+
+ @config.setter
+ def config(self, config: Config) -> None:
+ """Set config"""
+ self.set_config(config)
+
+ @property
+ def context(self) -> Context:
+ """Role context: role context > context"""
+ if self.private_context:
+ return self.private_context
+ return Context()
+
+ @context.setter
+ def context(self, context: Context) -> None:
+ """Set context"""
+ self.set_context(context)
+
+ @property
+ def llm(self) -> BaseLLM:
+ """Role llm: if not existed, init from role.config"""
+ # print(f"class:{self.__class__.__name__}({self.name}), llm: {self._llm}, llm_config: {self._llm_config}")
+ if not self.private_llm:
+ self.private_llm = self.context.llm_with_cost_manager_from_llm_config(self.config.llm)
+ return self.private_llm
+
+ @llm.setter
+ def llm(self, llm: BaseLLM) -> None:
+ """Set llm"""
+ self.private_llm = llm
diff --git a/metagpt/document_store/base_store.py b/metagpt/document_store/base_store.py
index b719d1083..ddc1d626b 100644
--- a/metagpt/document_store/base_store.py
+++ b/metagpt/document_store/base_store.py
@@ -8,8 +8,6 @@
from abc import ABC, abstractmethod
from pathlib import Path
-from metagpt.config import Config
-
class BaseStore(ABC):
"""FIXME: consider add_index, set_index and think about granularity."""
@@ -31,7 +29,6 @@ class LocalStore(BaseStore, ABC):
def __init__(self, raw_data_path: Path, cache_dir: Path = None):
if not raw_data_path:
raise FileNotFoundError
- self.config = Config()
self.raw_data_path = raw_data_path
self.fname = self.raw_data_path.stem
if not cache_dir:
diff --git a/metagpt/document_store/faiss_store.py b/metagpt/document_store/faiss_store.py
index 1271f1c23..2359917d5 100644
--- a/metagpt/document_store/faiss_store.py
+++ b/metagpt/document_store/faiss_store.py
@@ -9,14 +9,13 @@ import asyncio
from pathlib import Path
from typing import Optional
-from langchain.embeddings import OpenAIEmbeddings
from langchain.vectorstores import FAISS
from langchain_core.embeddings import Embeddings
-from metagpt.config import CONFIG
from metagpt.document import IndexableDocument
from metagpt.document_store.base_store import LocalStore
from metagpt.logs import logger
+from metagpt.utils.embedding import get_embedding
class FaissStore(LocalStore):
@@ -25,9 +24,7 @@ class FaissStore(LocalStore):
):
self.meta_col = meta_col
self.content_col = content_col
- self.embedding = embedding or OpenAIEmbeddings(
- openai_api_key=CONFIG.openai_api_key, openai_api_base=CONFIG.openai_base_url
- )
+ self.embedding = embedding or get_embedding()
super().__init__(raw_data, cache_dir)
def _load(self) -> Optional["FaissStore"]:
diff --git a/metagpt/environment.py b/metagpt/environment.py
index ddb9ad9dd..5a2dd339b 100644
--- a/metagpt/environment.py
+++ b/metagpt/environment.py
@@ -12,16 +12,15 @@
functionality is to be consolidated into the `Environment` class.
"""
import asyncio
-from pathlib import Path
from typing import Iterable, Set
from pydantic import BaseModel, ConfigDict, Field, SerializeAsAny, model_validator
-from metagpt.config import CONFIG
+from metagpt.context import Context
from metagpt.logs import logger
from metagpt.roles.role import Role
from metagpt.schema import Message
-from metagpt.utils.common import is_subscribed, read_json_file, write_json_file
+from metagpt.utils.common import is_send_to
class Environment(BaseModel):
@@ -33,58 +32,22 @@ class Environment(BaseModel):
desc: str = Field(default="") # 环境描述
roles: dict[str, SerializeAsAny[Role]] = Field(default_factory=dict, validate_default=True)
- members: dict[Role, Set] = Field(default_factory=dict, exclude=True)
+ member_addrs: dict[Role, Set] = Field(default_factory=dict, exclude=True)
history: str = "" # For debug
+ context: Context = Field(default_factory=Context, exclude=True)
@model_validator(mode="after")
def init_roles(self):
self.add_roles(self.roles.values())
return self
- def serialize(self, stg_path: Path):
- roles_path = stg_path.joinpath("roles.json")
- roles_info = []
- for role_key, role in self.roles.items():
- roles_info.append(
- {
- "role_class": role.__class__.__name__,
- "module_name": role.__module__,
- "role_name": role.name,
- "role_sub_tags": list(self.members.get(role)),
- }
- )
- role.serialize(stg_path=stg_path.joinpath(f"roles/{role.__class__.__name__}_{role.name}"))
- write_json_file(roles_path, roles_info)
-
- history_path = stg_path.joinpath("history.json")
- write_json_file(history_path, {"content": self.history})
-
- @classmethod
- def deserialize(cls, stg_path: Path) -> "Environment":
- """stg_path: ./storage/team/environment/"""
- roles_path = stg_path.joinpath("roles.json")
- roles_info = read_json_file(roles_path)
- roles = []
- for role_info in roles_info:
- # role stored in ./environment/roles/{role_class}_{role_name}
- role_path = stg_path.joinpath(f"roles/{role_info.get('role_class')}_{role_info.get('role_name')}")
- role = Role.deserialize(role_path)
- roles.append(role)
-
- history = read_json_file(stg_path.joinpath("history.json"))
- history = history.get("content")
-
- environment = Environment(**{"history": history})
- environment.add_roles(roles)
-
- return environment
-
def add_role(self, role: Role):
"""增加一个在当前环境的角色
Add a role in the current environment
"""
self.roles[role.profile] = role
role.set_env(self)
+ role.context = self.context
def add_roles(self, roles: Iterable[Role]):
"""增加一批在当前环境的角色
@@ -95,6 +58,7 @@ class Environment(BaseModel):
for role in roles: # setup system message with roles
role.set_env(self)
+ role.context = self.context
def publish_message(self, message: Message, peekable: bool = True) -> bool:
"""
@@ -108,8 +72,8 @@ class Environment(BaseModel):
logger.debug(f"publish_message: {message.dump()}")
found = False
# According to the routing feature plan in Chapter 2.2.3.2 of RFC 113
- for role, subscription in self.members.items():
- if is_subscribed(message, subscription):
+ for role, addrs in self.member_addrs.items():
+ if is_send_to(message, addrs):
role.put_message(message)
found = True
if not found:
@@ -154,15 +118,14 @@ class Environment(BaseModel):
return False
return True
- def get_subscription(self, obj):
- """Get the labels for messages to be consumed by the object."""
- return self.members.get(obj, {})
+ def get_addresses(self, obj):
+ """Get the addresses of the object."""
+ return self.member_addrs.get(obj, {})
- def set_subscription(self, obj, tags):
- """Set the labels for message to be consumed by the object"""
- self.members[obj] = tags
+ def set_addresses(self, obj, addresses):
+ """Set the addresses of the object"""
+ self.member_addrs[obj] = addresses
- @staticmethod
- def archive(auto_archive=True):
- if auto_archive and CONFIG.git_repo:
- CONFIG.git_repo.archive()
+ def archive(self, auto_archive=True):
+ if auto_archive and self.context.git_repo:
+ self.context.git_repo.archive()
diff --git a/metagpt/learn/skill_loader.py b/metagpt/learn/skill_loader.py
index 7383af66d..bcf28bb87 100644
--- a/metagpt/learn/skill_loader.py
+++ b/metagpt/learn/skill_loader.py
@@ -13,7 +13,7 @@ import aiofiles
import yaml
from pydantic import BaseModel, Field
-from metagpt.config import CONFIG
+from metagpt.context import Context
class Example(BaseModel):
@@ -73,14 +73,15 @@ class SkillsDeclaration(BaseModel):
skill_data = yaml.safe_load(data)
return SkillsDeclaration(**skill_data)
- def get_skill_list(self, entity_name: str = "Assistant") -> Dict:
+ def get_skill_list(self, entity_name: str = "Assistant", context: Context = None) -> Dict:
"""Return the skill name based on the skill description."""
entity = self.entities.get(entity_name)
if not entity:
return {}
# List of skills that the agent chooses to activate.
- agent_skills = CONFIG.agent_skills
+ ctx = context or Context()
+ agent_skills = ctx.kwargs.agent_skills
if not agent_skills:
return {}
diff --git a/metagpt/learn/text_to_embedding.py b/metagpt/learn/text_to_embedding.py
index 26dab0419..f859ab638 100644
--- a/metagpt/learn/text_to_embedding.py
+++ b/metagpt/learn/text_to_embedding.py
@@ -6,19 +6,19 @@
@File : text_to_embedding.py
@Desc : Text-to-Embedding skill, which provides text-to-embedding functionality.
"""
-
-from metagpt.config import CONFIG
+import metagpt.config2
+from metagpt.config2 import Config
from metagpt.tools.openai_text_to_embedding import oas3_openai_text_to_embedding
-async def text_to_embedding(text, model="text-embedding-ada-002", openai_api_key="", **kwargs):
+async def text_to_embedding(text, model="text-embedding-ada-002", config: Config = metagpt.config2.config):
"""Text to embedding
:param text: The text used for embedding.
:param model: One of ['text-embedding-ada-002'], ID of the model to use. For more details, checkout: `https://api.openai.com/v1/models`.
- :param openai_api_key: OpenAI API key, For more details, checkout: `https://platform.openai.com/account/api-keys`
+ :param config: OpenAI config with API key, For more details, checkout: `https://platform.openai.com/account/api-keys`
:return: A json object of :class:`ResultEmbedding` class if successful, otherwise `{}`.
"""
- if CONFIG.OPENAI_API_KEY or openai_api_key:
- return await oas3_openai_text_to_embedding(text, model=model, openai_api_key=openai_api_key)
- raise EnvironmentError
+ openai_api_key = config.get_openai_llm().api_key
+ proxy = config.get_openai_llm().proxy
+ return await oas3_openai_text_to_embedding(text, model=model, openai_api_key=openai_api_key, proxy=proxy)
diff --git a/metagpt/learn/text_to_image.py b/metagpt/learn/text_to_image.py
index c3c62fb67..e2fac7647 100644
--- a/metagpt/learn/text_to_image.py
+++ b/metagpt/learn/text_to_image.py
@@ -8,33 +8,37 @@
"""
import base64
-from metagpt.config import CONFIG
+import metagpt.config2
+from metagpt.config2 import Config
from metagpt.const import BASE64_FORMAT
+from metagpt.llm import LLM
from metagpt.tools.metagpt_text_to_image import oas3_metagpt_text_to_image
from metagpt.tools.openai_text_to_image import oas3_openai_text_to_image
from metagpt.utils.s3 import S3
-async def text_to_image(text, size_type: str = "512x512", openai_api_key="", model_url="", **kwargs):
+async def text_to_image(text, size_type: str = "512x512", config: Config = metagpt.config2.config):
"""Text to image
:param text: The text used for image conversion.
- :param openai_api_key: OpenAI API key, For more details, checkout: `https://platform.openai.com/account/api-keys`
:param size_type: If using OPENAI, the available size options are ['256x256', '512x512', '1024x1024'], while for MetaGPT, the options are ['512x512', '512x768'].
- :param model_url: MetaGPT model url
+ :param config: Config
:return: The image data is returned in Base64 encoding.
"""
image_declaration = "data:image/png;base64,"
- if CONFIG.METAGPT_TEXT_TO_IMAGE_MODEL_URL or model_url:
+
+ model_url = config.METAGPT_TEXT_TO_IMAGE_MODEL_URL
+ if model_url:
binary_data = await oas3_metagpt_text_to_image(text, size_type, model_url)
- elif CONFIG.OPENAI_API_KEY or openai_api_key:
- binary_data = await oas3_openai_text_to_image(text, size_type)
+ elif config.get_openai_llm():
+ llm = LLM(llm_config=config.get_openai_llm())
+ binary_data = await oas3_openai_text_to_image(text, size_type, llm=llm)
else:
raise ValueError("Missing necessary parameters.")
base64_data = base64.b64encode(binary_data).decode("utf-8")
- s3 = S3()
- url = await s3.cache(data=base64_data, file_ext=".png", format=BASE64_FORMAT) if s3.is_valid else ""
+ s3 = S3(config.s3)
+ url = await s3.cache(data=base64_data, file_ext=".png", format=BASE64_FORMAT)
if url:
return f""
return image_declaration + base64_data if base64_data else ""
diff --git a/metagpt/learn/text_to_speech.py b/metagpt/learn/text_to_speech.py
index ecd00c724..37e56eaff 100644
--- a/metagpt/learn/text_to_speech.py
+++ b/metagpt/learn/text_to_speech.py
@@ -6,8 +6,8 @@
@File : text_to_speech.py
@Desc : Text-to-Speech skill, which provides text-to-speech functionality
"""
-
-from metagpt.config import CONFIG
+import metagpt.config2
+from metagpt.config2 import Config
from metagpt.const import BASE64_FORMAT
from metagpt.tools.azure_tts import oas3_azsure_tts
from metagpt.tools.iflytek_tts import oas3_iflytek_tts
@@ -20,12 +20,7 @@ async def text_to_speech(
voice="zh-CN-XiaomoNeural",
style="affectionate",
role="Girl",
- subscription_key="",
- region="",
- iflytek_app_id="",
- iflytek_api_key="",
- iflytek_api_secret="",
- **kwargs,
+ config: Config = metagpt.config2.config,
):
"""Text to speech
For more details, check out:`https://learn.microsoft.com/en-us/azure/ai-services/speech-service/language-support?tabs=tts`
@@ -44,23 +39,27 @@ async def text_to_speech(
"""
- if (CONFIG.AZURE_TTS_SUBSCRIPTION_KEY and CONFIG.AZURE_TTS_REGION) or (subscription_key and region):
+ subscription_key = config.AZURE_TTS_SUBSCRIPTION_KEY
+ region = config.AZURE_TTS_REGION
+ if subscription_key and region:
audio_declaration = "data:audio/wav;base64,"
base64_data = await oas3_azsure_tts(text, lang, voice, style, role, subscription_key, region)
- s3 = S3()
- url = await s3.cache(data=base64_data, file_ext=".wav", format=BASE64_FORMAT) if s3.is_valid else ""
+ s3 = S3(config.s3)
+ url = await s3.cache(data=base64_data, file_ext=".wav", format=BASE64_FORMAT)
if url:
return f"[{text}]({url})"
return audio_declaration + base64_data if base64_data else base64_data
- if (CONFIG.IFLYTEK_APP_ID and CONFIG.IFLYTEK_API_KEY and CONFIG.IFLYTEK_API_SECRET) or (
- iflytek_app_id and iflytek_api_key and iflytek_api_secret
- ):
+
+ iflytek_app_id = config.IFLYTEK_APP_ID
+ iflytek_api_key = config.IFLYTEK_API_KEY
+ iflytek_api_secret = config.IFLYTEK_API_SECRET
+ if iflytek_app_id and iflytek_api_key and iflytek_api_secret:
audio_declaration = "data:audio/mp3;base64,"
base64_data = await oas3_iflytek_tts(
text=text, app_id=iflytek_app_id, api_key=iflytek_api_key, api_secret=iflytek_api_secret
)
- s3 = S3()
- url = await s3.cache(data=base64_data, file_ext=".mp3", format=BASE64_FORMAT) if s3.is_valid else ""
+ s3 = S3(config.s3)
+ url = await s3.cache(data=base64_data, file_ext=".mp3", format=BASE64_FORMAT)
if url:
return f"[{text}]({url})"
return audio_declaration + base64_data if base64_data else base64_data
diff --git a/metagpt/llm.py b/metagpt/llm.py
index 76dd5a0f8..a3fc5613a 100644
--- a/metagpt/llm.py
+++ b/metagpt/llm.py
@@ -5,20 +5,16 @@
@Author : alexanderwu
@File : llm.py
"""
-
from typing import Optional
-from metagpt.config import CONFIG, LLMProviderEnum
+from metagpt.configs.llm_config import LLMConfig
+from metagpt.context import Context
from metagpt.provider.base_llm import BaseLLM
-from metagpt.provider.human_provider import HumanProvider
-from metagpt.provider.llm_provider_registry import LLM_REGISTRY
-
-_ = HumanProvider() # Avoid pre-commit error
-def LLM(provider: Optional[LLMProviderEnum] = None) -> BaseLLM:
- """get the default llm provider"""
- if provider is None:
- provider = CONFIG.get_default_llm_provider_enum()
-
- return LLM_REGISTRY.get_provider(provider)
+def LLM(llm_config: Optional[LLMConfig] = None, context: Context = None) -> BaseLLM:
+ """get the default llm provider if name is None"""
+ ctx = context or Context()
+ if llm_config is not None:
+ ctx.llm_with_cost_manager_from_llm_config(llm_config)
+ return ctx.llm()
diff --git a/metagpt/memory/brain_memory.py b/metagpt/memory/brain_memory.py
index ff29eaddb..044b0b359 100644
--- a/metagpt/memory/brain_memory.py
+++ b/metagpt/memory/brain_memory.py
@@ -14,8 +14,8 @@ from typing import Dict, List, Optional
from pydantic import BaseModel, Field
-from metagpt.config import CONFIG
-from metagpt.const import DEFAULT_LANGUAGE, DEFAULT_MAX_TOKENS, DEFAULT_TOKEN_SIZE
+from metagpt.config2 import config
+from metagpt.const import DEFAULT_MAX_TOKENS, DEFAULT_TOKEN_SIZE
from metagpt.logs import logger
from metagpt.provider import MetaGPTLLM
from metagpt.provider.base_llm import BaseLLM
@@ -29,9 +29,9 @@ class BrainMemory(BaseModel):
historical_summary: str = ""
last_history_id: str = ""
is_dirty: bool = False
- last_talk: str = None
+ last_talk: Optional[str] = None
cacheable: bool = True
- llm: Optional[BaseLLM] = None
+ llm: Optional[BaseLLM] = Field(default=None, exclude=True)
class Config:
arbitrary_types_allowed = True
@@ -56,8 +56,8 @@ class BrainMemory(BaseModel):
@staticmethod
async def loads(redis_key: str) -> "BrainMemory":
- redis = Redis()
- if not redis.is_valid or not redis_key:
+ redis = Redis(config.redis)
+ if not redis_key:
return BrainMemory()
v = await redis.get(key=redis_key)
logger.debug(f"REDIS GET {redis_key} {v}")
@@ -70,8 +70,8 @@ class BrainMemory(BaseModel):
async def dumps(self, redis_key: str, timeout_sec: int = 30 * 60):
if not self.is_dirty:
return
- redis = Redis()
- if not redis.is_valid or not redis_key:
+ redis = Redis(config.redis)
+ if not redis_key:
return False
v = self.model_dump_json()
if self.cacheable:
@@ -83,7 +83,7 @@ class BrainMemory(BaseModel):
def to_redis_key(prefix: str, user_id: str, chat_id: str):
return f"{prefix}:{user_id}:{chat_id}"
- async def set_history_summary(self, history_summary, redis_key, redis_conf):
+ async def set_history_summary(self, history_summary, redis_key):
if self.historical_summary == history_summary:
if self.is_dirty:
await self.dumps(redis_key=redis_key)
@@ -140,7 +140,7 @@ class BrainMemory(BaseModel):
return text
summary = await self._summarize(text=text, max_words=max_words, keep_language=keep_language, limit=limit)
if summary:
- await self.set_history_summary(history_summary=summary, redis_key=CONFIG.REDIS_KEY, redis_conf=CONFIG.REDIS)
+ await self.set_history_summary(history_summary=summary, redis_key=config.redis_key)
return summary
raise ValueError(f"text too long:{text_length}")
@@ -164,7 +164,7 @@ class BrainMemory(BaseModel):
msgs.reverse()
self.history = msgs
self.is_dirty = True
- await self.dumps(redis_key=CONFIG.REDIS_KEY)
+ await self.dumps(redis_key=config.redis.key)
self.is_dirty = False
return BrainMemory.to_metagpt_history_format(self.history)
@@ -181,7 +181,7 @@ class BrainMemory(BaseModel):
summary = await self.summarize(llm=llm, max_words=500)
- language = CONFIG.language or DEFAULT_LANGUAGE
+ language = config.language
command = f"Translate the above summary into a {language} title of less than {max_words} words."
summaries = [summary, command]
msg = "\n".join(summaries)
diff --git a/metagpt/memory/longterm_memory.py b/metagpt/memory/longterm_memory.py
index b54653970..5a139a93b 100644
--- a/metagpt/memory/longterm_memory.py
+++ b/metagpt/memory/longterm_memory.py
@@ -2,7 +2,6 @@
# -*- coding: utf-8 -*-
"""
@Desc : the implement of Long-term memory
-@Modified By: mashenquan, 2023/8/20. Remove global configuration `CONFIG`, enable configuration support for business isolation.
"""
from typing import Optional
diff --git a/metagpt/memory/memory.py b/metagpt/memory/memory.py
index 593409648..580361d33 100644
--- a/metagpt/memory/memory.py
+++ b/metagpt/memory/memory.py
@@ -7,19 +7,13 @@
@Modified By: mashenquan, 2023-11-1. According to RFC 116: Updated the type of index key.
"""
from collections import defaultdict
-from pathlib import Path
from typing import DefaultDict, Iterable, Set
from pydantic import BaseModel, Field, SerializeAsAny
from metagpt.const import IGNORED_MESSAGE_ID
from metagpt.schema import Message
-from metagpt.utils.common import (
- any_to_str,
- any_to_str_set,
- read_json_file,
- write_json_file,
-)
+from metagpt.utils.common import any_to_str, any_to_str_set
class Memory(BaseModel):
@@ -29,22 +23,6 @@ class Memory(BaseModel):
index: DefaultDict[str, list[SerializeAsAny[Message]]] = Field(default_factory=lambda: defaultdict(list))
ignore_id: bool = False
- def serialize(self, stg_path: Path):
- """stg_path = ./storage/team/environment/ or ./storage/team/environment/roles/{role_class}_{role_name}/"""
- memory_path = stg_path.joinpath("memory.json")
- storage = self.model_dump()
- write_json_file(memory_path, storage)
-
- @classmethod
- def deserialize(cls, stg_path: Path) -> "Memory":
- """stg_path = ./storage/team/environment/ or ./storage/team/environment/roles/{role_class}_{role_name}/"""
- memory_path = stg_path.joinpath("memory.json")
-
- memory_dict = read_json_file(memory_path)
- memory = Memory(**memory_dict)
-
- return memory
-
def add(self, message: Message):
"""Add a new message to storage, while updating the index"""
if self.ignore_id:
diff --git a/metagpt/memory/memory_storage.py b/metagpt/memory/memory_storage.py
index 1850e0ea0..c029d027b 100644
--- a/metagpt/memory/memory_storage.py
+++ b/metagpt/memory/memory_storage.py
@@ -2,7 +2,6 @@
# -*- coding: utf-8 -*-
"""
@Desc : the implement of memory storage
-@Modified By: mashenquan, 2023/8/20. Remove global configuration `CONFIG`, enable configuration support for business isolation.
"""
from pathlib import Path
diff --git a/metagpt/plan/planner.py b/metagpt/plan/planner.py
index 87492e455..0d8870fd3 100644
--- a/metagpt/plan/planner.py
+++ b/metagpt/plan/planner.py
@@ -32,8 +32,8 @@ class Planner(BaseModel):
auto_run: bool = False
use_tools: bool = False
- def __init__(self, goal: str, **kwargs):
- plan = Plan(goal=goal)
+ def __init__(self, goal: str = "", plan: Plan = None, **kwargs):
+ plan = plan or Plan(goal=goal)
super().__init__(plan=plan, **kwargs)
@property
@@ -44,6 +44,48 @@ class Planner(BaseModel):
def current_task_id(self):
return self.plan.current_task_id
+ async def update_plan(self, goal: str = "", max_tasks: int = 3, max_retries: int = 3):
+ if goal:
+ self.plan = Plan(goal=goal)
+
+ plan_confirmed = False
+ while not plan_confirmed:
+ context = self.get_useful_memories()
+ rsp = await WritePlan().run(context, max_tasks=max_tasks, use_tools=self.use_tools)
+ self.working_memory.add(Message(content=rsp, role="assistant", cause_by=WritePlan))
+
+ # precheck plan before asking reviews
+ is_plan_valid, error = precheck_update_plan_from_rsp(rsp, self.plan)
+ if not is_plan_valid and max_retries > 0:
+ error_msg = f"The generated plan is not valid with error: {error}, try regenerating, remember to generate either the whole plan or the single changed task only"
+ logger.warning(error_msg)
+ self.working_memory.add(Message(content=error_msg, role="assistant", cause_by=WritePlan))
+ max_retries -= 1
+ continue
+
+ _, plan_confirmed = await self.ask_review(trigger=ReviewConst.TASK_REVIEW_TRIGGER)
+
+ update_plan_from_rsp(rsp=rsp, current_plan=self.plan)
+
+ self.working_memory.clear()
+
+ async def process_task_result(self, task_result: TaskResult):
+ # ask for acceptance, users can other refuse and change tasks in the plan
+ review, task_result_confirmed = await self.ask_review(task_result)
+
+ if task_result_confirmed:
+ # tick off this task and record progress
+ await self.confirm_task(self.current_task, task_result, review)
+
+ elif "redo" in review:
+ # Ask the Role to redo this task with help of review feedback,
+ # useful when the code run is successful but the procedure or result is not what we want
+ pass # simply pass, not confirming the result
+
+ else:
+ # update plan according to user's feedback and to take on changed tasks
+ await self.update_plan()
+
async def ask_review(
self, task_result: TaskResult = None, auto_run: bool = None, trigger: str = ReviewConst.TASK_REVIEW_TRIGGER
):
@@ -74,28 +116,6 @@ class Planner(BaseModel):
self.working_memory.add(Message(content=review, role="user", cause_by=AskReview))
await self.update_plan(review)
- async def update_plan(self, max_tasks: int = 3, max_retries: int = 3):
- plan_confirmed = False
- while not plan_confirmed:
- context = self.get_useful_memories()
- rsp = await WritePlan().run(context, max_tasks=max_tasks, use_tools=self.use_tools)
- self.working_memory.add(Message(content=rsp, role="assistant", cause_by=WritePlan))
-
- # precheck plan before asking reviews
- is_plan_valid, error = precheck_update_plan_from_rsp(rsp, self.plan)
- if not is_plan_valid and max_retries > 0:
- error_msg = f"The generated plan is not valid with error: {error}, try regenerating, remember to generate either the whole plan or the single changed task only"
- logger.warning(error_msg)
- self.working_memory.add(Message(content=error_msg, role="assistant", cause_by=WritePlan))
- max_retries -= 1
- continue
-
- _, plan_confirmed = await self.ask_review(trigger=ReviewConst.TASK_REVIEW_TRIGGER)
-
- update_plan_from_rsp(rsp=rsp, current_plan=self.plan)
-
- self.working_memory.clear()
-
def get_useful_memories(self, task_exclude_field=None) -> list[Message]:
"""find useful memories only to reduce context length and improve performance"""
# TODO dataset description , code steps
diff --git a/metagpt/prompts/ml_engineer.py b/metagpt/prompts/ml_action.py
similarity index 64%
rename from metagpt/prompts/ml_engineer.py
rename to metagpt/prompts/ml_action.py
index ac95e14bd..582b01146 100644
--- a/metagpt/prompts/ml_engineer.py
+++ b/metagpt/prompts/ml_action.py
@@ -2,7 +2,7 @@
# -*- coding: utf-8 -*-
# @Time : 2023/11/24 15:43
# @Author : lidanyang
-# @File : ml_engineer
+# @File : ml_action
# @Desc :
UPDATE_DATA_COLUMNS = """
# Background
@@ -49,85 +49,6 @@ Output the information in a JSON format, as shown in this example:
- Don't contain specific values or examples found in the data column.
"""
-ASSIGN_TASK_TYPE_PROMPT = """
-Please assign a task type to each task in the list below from the given categories:
-{task_list}
-
-## All Task Type:
-{task_type_desc}
-"""
-
-ASSIGN_TASK_TYPE_CONFIG = {
- "name": "assign_task_type",
- "description": "Assign task type to each task by order.",
- "parameters": {
- "type": "object",
- "properties": {
- "task_type": {
- "type": "array",
- "description": "List of task type. The length should as long as task list",
- "items": {
- "type": "string",
- },
- },
- },
- "required": ["task_type"],
- },
-}
-
-TOOL_RECOMMENDATION_PROMPT = """
-## User Requirement:
-{current_task}
-
-## Task
-Recommend up to five tools from 'Available Tools' that can help solve the 'User Requirement'.
-This is a detailed code steps for current task. You can refer to it when recommending tools.
-{code_steps}
-
-## Available Tools:
-{available_tools}
-
-## Tool Selection and Instructions:
-- Select tools most relevant to completing the 'User Requirement'.
-- If you believe that no tools are suitable, indicate with an empty list.
-- Only list the names of the tools, not the full schema of each tool.
-- Ensure selected tools are listed in 'Available Tools'.
-"""
-
-SELECT_FUNCTION_TOOLS = {
- "name": "select_function_tools",
- "description": "For current task, select suitable tools for it.",
- "parameters": {
- "type": "object",
- "properties": {
- "recommend_tools": {
- "type": "array",
- "description": "List of tool names. Empty list if no tool is suitable.",
- "items": {
- "type": "string",
- },
- },
- },
- "required": ["recommend_tools"],
- },
-}
-
-CODE_GENERATOR_WITH_TOOLS = {
- "name": "add_subtask_code",
- "description": "Add new code cell of current task to the end of an active Jupyter notebook.",
- "parameters": {
- "type": "object",
- "properties": {
- "code": {
- "type": "string",
- "description": "The code to be added to a new cell in jupyter.",
- },
- },
- "required": ["code"],
- },
-}
-
-
PRINT_DATA_COLUMNS = {
"name": "print_column_info",
"description": "Print the latest column information after 'Done Tasks' code if first read or data changed.",
@@ -189,24 +110,6 @@ model.fit(train, y_train)
- The output code should contain all steps implemented in 'Code Steps'.
"""
-TOOL_USAGE_PROMPT = """
-# Instruction
-Write complete code for 'Current Task'. And avoid duplicating code from finished tasks, such as repeated import of packages, reading data, etc.
-Specifically, {tool_type_usage_prompt}
-
-# Capabilities
-- You can utilize pre-defined tools in any code lines from 'Available Tools' in the form of Python Class.
-- You can freely combine the use of any other public packages, like sklearn, numpy, pandas, etc..
-
-# Available Tools (can be empty):
-Each Class tool is described in JSON format. When you call a tool, import the tool first.
-{tool_schemas}
-
-# Constraints:
-- Ensure the output new code is executable in the same Jupyter notebook with previous tasks code have been executed.
-- Always prioritize using pre-defined tools for the same functionality.
-"""
-
ML_TOOL_USAGE_PROMPT = """
# Background
As a data scientist, you need to help user to achieve their goal [{user_requirement}] step-by-step in an continuous Jupyter notebook.
diff --git a/metagpt/prompts/tool_types.py b/metagpt/prompts/tool_types.py
index 42d9c1ece..381fb25ad 100644
--- a/metagpt/prompts/tool_types.py
+++ b/metagpt/prompts/tool_types.py
@@ -25,7 +25,7 @@ The current task is about feature engineering. when performing it, please adhere
# Prompt for using tools of "model_train" type
MODEL_TRAIN_PROMPT = """
The current task is about training a model, please ensure high performance:
-- Keep in mind that your user prioritizes results and is highly focused on model performance. So, when needed, feel free to use models of any complexity to improve effectiveness, such as lightGBM, XGBoost, CatBoost, etc.
+- Keep in mind that your user prioritizes results and is highly focused on model performance. So, when needed, feel free to use models of any complexity to improve effectiveness, such as XGBoost, CatBoost, etc.
- If non-numeric columns exist, perform label encode together with all steps.
- Use the data from previous task result directly, do not mock or reload data yourself.
- Set suitable hyperparameters for the model, make metrics as high as possible.
diff --git a/metagpt/prompts/write_analysis_code.py b/metagpt/prompts/write_analysis_code.py
new file mode 100644
index 000000000..4c8a5081e
--- /dev/null
+++ b/metagpt/prompts/write_analysis_code.py
@@ -0,0 +1,95 @@
+ASSIGN_TASK_TYPE_PROMPT = """
+Please assign a task type to each task in the list below from the given categories:
+{task_list}
+
+## All Task Type:
+{task_type_desc}
+"""
+
+ASSIGN_TASK_TYPE_CONFIG = {
+ "name": "assign_task_type",
+ "description": "Assign task type to each task by order.",
+ "parameters": {
+ "type": "object",
+ "properties": {
+ "task_type": {
+ "type": "array",
+ "description": "List of task type. The length should as long as task list",
+ "items": {
+ "type": "string",
+ },
+ },
+ },
+ "required": ["task_type"],
+ },
+}
+
+TOOL_RECOMMENDATION_PROMPT = """
+## User Requirement:
+{current_task}
+
+## Task
+Recommend up to five tools from 'Available Tools' that can help solve the 'User Requirement'.
+This is a detailed code steps for current task. You can refer to it when recommending tools.
+{code_steps}
+
+## Available Tools:
+{available_tools}
+
+## Tool Selection and Instructions:
+- Select tools most relevant to completing the 'User Requirement'.
+- If you believe that no tools are suitable, indicate with an empty list.
+- Only list the names of the tools, not the full schema of each tool.
+- Ensure selected tools are listed in 'Available Tools'.
+"""
+
+SELECT_FUNCTION_TOOLS = {
+ "name": "select_function_tools",
+ "description": "For current task, select suitable tools for it.",
+ "parameters": {
+ "type": "object",
+ "properties": {
+ "recommend_tools": {
+ "type": "array",
+ "description": "List of tool names. Empty list if no tool is suitable.",
+ "items": {
+ "type": "string",
+ },
+ },
+ },
+ "required": ["recommend_tools"],
+ },
+}
+
+CODE_GENERATOR_WITH_TOOLS = {
+ "name": "add_subtask_code",
+ "description": "Add new code cell of current task to the end of an active Jupyter notebook.",
+ "parameters": {
+ "type": "object",
+ "properties": {
+ "code": {
+ "type": "string",
+ "description": "The code to be added to a new cell in jupyter.",
+ },
+ },
+ "required": ["code"],
+ },
+}
+
+TOOL_USAGE_PROMPT = """
+# Instruction
+Write complete code for 'Current Task'. And avoid duplicating code from finished tasks, such as repeated import of packages, reading data, etc.
+Specifically, {tool_type_usage_prompt}
+
+# Capabilities
+- You can utilize pre-defined tools in any code lines from 'Available Tools' in the form of Python Class.
+- You can freely combine the use of any other public packages, like sklearn, numpy, pandas, etc..
+
+# Available Tools (can be empty):
+Each Class tool is described in JSON format. When you call a tool, import the tool first.
+{tool_schemas}
+
+# Constraints:
+- Ensure the output new code is executable in the same Jupyter notebook with previous tasks code have been executed.
+- Always prioritize using pre-defined tools for the same functionality.
+"""
diff --git a/metagpt/provider/__init__.py b/metagpt/provider/__init__.py
index 28157a4e2..675734811 100644
--- a/metagpt/provider/__init__.py
+++ b/metagpt/provider/__init__.py
@@ -14,6 +14,8 @@ from metagpt.provider.openai_api import OpenAILLM
from metagpt.provider.zhipuai_api import ZhiPuAILLM
from metagpt.provider.azure_openai_api import AzureOpenAILLM
from metagpt.provider.metagpt_api import MetaGPTLLM
+from metagpt.provider.human_provider import HumanProvider
+from metagpt.provider.spark_api import SparkLLM
__all__ = [
"FireworksLLM",
@@ -24,4 +26,6 @@ __all__ = [
"AzureOpenAILLM",
"MetaGPTLLM",
"OllamaLLM",
+ "HumanProvider",
+ "SparkLLM",
]
diff --git a/metagpt/provider/anthropic_api.py b/metagpt/provider/anthropic_api.py
index b9d7d9e38..f31c2d04d 100644
--- a/metagpt/provider/anthropic_api.py
+++ b/metagpt/provider/anthropic_api.py
@@ -9,12 +9,15 @@
import anthropic
from anthropic import Anthropic, AsyncAnthropic
-from metagpt.config import CONFIG
+from metagpt.configs.llm_config import LLMConfig
class Claude2:
+ def __init__(self, config: LLMConfig):
+ self.config = config
+
def ask(self, prompt: str) -> str:
- client = Anthropic(api_key=CONFIG.anthropic_api_key)
+ client = Anthropic(api_key=self.config.api_key)
res = client.completions.create(
model="claude-2",
@@ -24,7 +27,7 @@ class Claude2:
return res.completion
async def aask(self, prompt: str) -> str:
- aclient = AsyncAnthropic(api_key=CONFIG.anthropic_api_key)
+ aclient = AsyncAnthropic(api_key=self.config.api_key)
res = await aclient.completions.create(
model="claude-2",
diff --git a/metagpt/provider/azure_openai_api.py b/metagpt/provider/azure_openai_api.py
index d15d1c82e..6dc32d380 100644
--- a/metagpt/provider/azure_openai_api.py
+++ b/metagpt/provider/azure_openai_api.py
@@ -3,8 +3,6 @@
@Time : 2023/5/5 23:08
@Author : alexanderwu
@File : openai.py
-@Modified By: mashenquan, 2023/8/20. Remove global configuration `CONFIG`, enable configuration support for business isolation;
- Change cost control from global to company level.
@Modified By: mashenquan, 2023/11/21. Fix bug: ReadTimeout.
@Modified By: mashenquan, 2023/12/1. Fix bug: Unclosed connection caused by openai 0.x.
"""
@@ -13,12 +11,12 @@
from openai import AsyncAzureOpenAI
from openai._base_client import AsyncHttpxClientWrapper
-from metagpt.config import LLMProviderEnum
+from metagpt.configs.llm_config import LLMType
from metagpt.provider.llm_provider_registry import register_provider
from metagpt.provider.openai_api import OpenAILLM
-@register_provider(LLMProviderEnum.AZURE_OPENAI)
+@register_provider(LLMType.AZURE)
class AzureOpenAILLM(OpenAILLM):
"""
Check https://platform.openai.com/examples for examples
@@ -28,13 +26,13 @@ class AzureOpenAILLM(OpenAILLM):
kwargs = self._make_client_kwargs()
# https://learn.microsoft.com/zh-cn/azure/ai-services/openai/how-to/migration?tabs=python-new%2Cdalle-fix
self.aclient = AsyncAzureOpenAI(**kwargs)
- self.model = self.config.DEPLOYMENT_NAME # Used in _calc_usage & _cons_kwargs
+ self.model = self.config.model # Used in _calc_usage & _cons_kwargs
def _make_client_kwargs(self) -> dict:
kwargs = dict(
- api_key=self.config.OPENAI_API_KEY,
- api_version=self.config.OPENAI_API_VERSION,
- azure_endpoint=self.config.OPENAI_BASE_URL,
+ api_key=self.config.api_key,
+ api_version=self.config.api_version,
+ azure_endpoint=self.config.base_url,
)
# to use proxy, openai v1 needs http_client
diff --git a/metagpt/provider/base_llm.py b/metagpt/provider/base_llm.py
index dbef15fa1..65e70af00 100644
--- a/metagpt/provider/base_llm.py
+++ b/metagpt/provider/base_llm.py
@@ -8,15 +8,32 @@
"""
import json
from abc import ABC, abstractmethod
-from typing import Optional
+from typing import Optional, Union
+
+from openai import AsyncOpenAI
+
+from metagpt.configs.llm_config import LLMConfig
+from metagpt.logs import logger
+from metagpt.schema import Message
+from metagpt.utils.cost_manager import CostManager
class BaseLLM(ABC):
"""LLM API abstract class, requiring all inheritors to provide a series of standard capabilities"""
+ config: LLMConfig
use_system_prompt: bool = True
system_prompt = "You are a helpful assistant."
+ # OpenAI / Azure / Others
+ aclient: Optional[Union[AsyncOpenAI]] = None
+ cost_manager: Optional[CostManager] = None
+ model: Optional[str] = None
+
+ @abstractmethod
+ def __init__(self, config: LLMConfig):
+ pass
+
def _user_msg(self, msg: str) -> dict[str, str]:
return {"role": "user", "content": msg}
@@ -43,10 +60,13 @@ class BaseLLM(ABC):
if system_msgs:
message = self._system_msgs(system_msgs)
else:
- message = [self._default_system_msg()] if self.use_system_prompt else []
+ message = [self._default_system_msg()]
+ if not self.use_system_prompt:
+ message = []
if format_msgs:
message.extend(format_msgs)
message.append(self._user_msg(msg))
+ logger.debug(message)
rsp = await self.acompletion_text(message, stream=stream, timeout=timeout)
return rsp
@@ -63,10 +83,9 @@ class BaseLLM(ABC):
context.append(self._assistant_msg(rsp_text))
return self._extract_assistant_rsp(context)
- async def aask_code(self, msgs: list[str], timeout=3) -> str:
+ async def aask_code(self, messages: Union[str, Message, list[dict]], timeout=3) -> dict:
"""FIXME: No code segment filtering has been done here, and all results are actually displayed"""
- rsp_text = await self.aask_batch(msgs, timeout=timeout)
- return rsp_text
+ raise NotImplementedError
@abstractmethod
async def acompletion(self, messages: list[dict], timeout=3):
@@ -87,6 +106,10 @@ class BaseLLM(ABC):
"""Required to provide the first text of choice"""
return rsp.get("choices")[0]["message"]["content"]
+ def get_choice_delta_text(self, rsp: dict) -> str:
+ """Required to provide the first text of stream choice"""
+ return rsp.get("choices")[0]["delta"]["content"]
+
def get_choice_function(self, rsp: dict) -> dict:
"""Required to provide the first function of choice
:param dict rsp: OpenAI chat.comletion respond JSON, Note "message" must include "tool_calls",
diff --git a/metagpt/provider/fireworks_api.py b/metagpt/provider/fireworks_api.py
index f0af68818..d56453a85 100644
--- a/metagpt/provider/fireworks_api.py
+++ b/metagpt/provider/fireworks_api.py
@@ -15,7 +15,7 @@ from tenacity import (
wait_random_exponential,
)
-from metagpt.config import CONFIG, Config, LLMProviderEnum
+from metagpt.configs.llm_config import LLMConfig, LLMType
from metagpt.logs import logger
from metagpt.provider.llm_provider_registry import register_provider
from metagpt.provider.openai_api import OpenAILLM, log_and_reraise
@@ -64,44 +64,35 @@ class FireworksCostManager(CostManager):
token_costs = self.model_grade_token_costs(model)
cost = (prompt_tokens * token_costs["prompt"] + completion_tokens * token_costs["completion"]) / 1000000
self.total_cost += cost
- max_budget = CONFIG.max_budget if CONFIG.max_budget else CONFIG.cost_manager.max_budget
logger.info(
- f"Total running cost: ${self.total_cost:.4f} | Max budget: ${max_budget:.3f} | "
+ f"Total running cost: ${self.total_cost:.4f}"
f"Current cost: ${cost:.4f}, prompt_tokens: {prompt_tokens}, completion_tokens: {completion_tokens}"
)
- CONFIG.total_cost = self.total_cost
-@register_provider(LLMProviderEnum.FIREWORKS)
+@register_provider(LLMType.FIREWORKS)
class FireworksLLM(OpenAILLM):
- def __init__(self):
- self.config: Config = CONFIG
- self.__init_fireworks()
+ def __init__(self, config: LLMConfig):
+ super().__init__(config=config)
self.auto_max_tokens = False
- self._cost_manager = FireworksCostManager()
-
- def __init_fireworks(self):
- self.is_azure = False
- self.rpm = int(self.config.get("RPM", 10))
- self._init_client()
- self.model = self.config.fireworks_api_model # `self.model` should after `_make_client` to rewrite it
+ self.cost_manager = FireworksCostManager()
def _make_client_kwargs(self) -> dict:
- kwargs = dict(api_key=self.config.fireworks_api_key, base_url=self.config.fireworks_api_base)
+ kwargs = dict(api_key=self.config.api_key, base_url=self.config.base_url)
return kwargs
def _update_costs(self, usage: CompletionUsage):
if self.config.calc_usage and usage:
try:
- # use FireworksCostManager not CONFIG.cost_manager
- self._cost_manager.update_cost(usage.prompt_tokens, usage.completion_tokens, self.model)
+ # use FireworksCostManager not context.cost_manager
+ self.cost_manager.update_cost(usage.prompt_tokens, usage.completion_tokens, self.model)
except Exception as e:
logger.error(f"updating costs failed!, exp: {e}")
def get_costs(self) -> Costs:
- return self._cost_manager.get_costs()
+ return self.cost_manager.get_costs()
- async def _achat_completion_stream(self, messages: list[dict]) -> str:
+ async def _achat_completion_stream(self, messages: list[dict], timeout=3) -> str:
response: AsyncStream[ChatCompletionChunk] = await self.aclient.chat.completions.create(
**self._cons_kwargs(messages), stream=True
)
diff --git a/metagpt/provider/general_api_requestor.py b/metagpt/provider/general_api_requestor.py
index cf31fd629..500cd1426 100644
--- a/metagpt/provider/general_api_requestor.py
+++ b/metagpt/provider/general_api_requestor.py
@@ -79,10 +79,8 @@ class GeneralAPIRequestor(APIRequestor):
async def _interpret_async_response(
self, result: aiohttp.ClientResponse, stream: bool
) -> Tuple[Union[bytes, AsyncGenerator[bytes, None]], bool]:
- if stream and (
- "text/event-stream" in result.headers.get("Content-Type", "")
- or "application/x-ndjson" in result.headers.get("Content-Type", "")
- ):
+ content_type = result.headers.get("Content-Type", "")
+ if stream and ("text/event-stream" in content_type or "application/x-ndjson" in content_type):
# the `Content-Type` of ollama stream resp is "application/x-ndjson"
return (
self._interpret_response_line(line, result.status, result.headers, stream=True)
diff --git a/metagpt/provider/google_gemini_api.py b/metagpt/provider/google_gemini_api.py
index c36c677ef..6df814b55 100644
--- a/metagpt/provider/google_gemini_api.py
+++ b/metagpt/provider/google_gemini_api.py
@@ -19,7 +19,7 @@ from tenacity import (
wait_random_exponential,
)
-from metagpt.config import CONFIG, LLMProviderEnum
+from metagpt.configs.llm_config import LLMConfig, LLMType
from metagpt.logs import log_llm_stream, logger
from metagpt.provider.base_llm import BaseLLM
from metagpt.provider.llm_provider_registry import register_provider
@@ -41,21 +41,22 @@ class GeminiGenerativeModel(GenerativeModel):
return await self._async_client.count_tokens(model=self.model_name, contents=contents)
-@register_provider(LLMProviderEnum.GEMINI)
+@register_provider(LLMType.GEMINI)
class GeminiLLM(BaseLLM):
"""
Refs to `https://ai.google.dev/tutorials/python_quickstart`
"""
- def __init__(self):
+ def __init__(self, config: LLMConfig):
self.use_system_prompt = False # google gemini has no system prompt when use api
- self.__init_gemini(CONFIG)
+ self.__init_gemini(config)
+ self.config = config
self.model = "gemini-pro" # so far only one model
self.llm = GeminiGenerativeModel(model_name=self.model)
- def __init_gemini(self, config: CONFIG):
- genai.configure(api_key=config.gemini_api_key)
+ def __init_gemini(self, config: LLMConfig):
+ genai.configure(api_key=config.api_key)
def _user_msg(self, msg: str) -> dict[str, str]:
# Not to change BaseLLM default functions but update with Gemini's conversation format.
@@ -71,11 +72,11 @@ class GeminiLLM(BaseLLM):
def _update_costs(self, usage: dict):
"""update each request's token cost"""
- if CONFIG.calc_usage:
+ if self.config.calc_usage:
try:
prompt_tokens = int(usage.get("prompt_tokens", 0))
completion_tokens = int(usage.get("completion_tokens", 0))
- CONFIG.cost_manager.update_cost(prompt_tokens, completion_tokens, self.model)
+ self.cost_manager.update_cost(prompt_tokens, completion_tokens, self.model)
except Exception as e:
logger.error(f"google gemini updats costs failed! exp: {e}")
@@ -108,7 +109,7 @@ class GeminiLLM(BaseLLM):
self._update_costs(usage)
return resp
- async def acompletion(self, messages: list[dict]) -> dict:
+ async def acompletion(self, messages: list[dict], timeout=3) -> dict:
return await self._achat_completion(messages)
async def _achat_completion_stream(self, messages: list[dict]) -> str:
diff --git a/metagpt/provider/human_provider.py b/metagpt/provider/human_provider.py
index 59d236a3a..fe000b3a6 100644
--- a/metagpt/provider/human_provider.py
+++ b/metagpt/provider/human_provider.py
@@ -5,6 +5,7 @@ Author: garylin2099
"""
from typing import Optional
+from metagpt.configs.llm_config import LLMConfig
from metagpt.logs import logger
from metagpt.provider.base_llm import BaseLLM
@@ -14,6 +15,9 @@ class HumanProvider(BaseLLM):
This enables replacing LLM anywhere in the framework with a human, thus introducing human interaction
"""
+ def __init__(self, config: LLMConfig):
+ pass
+
def ask(self, msg: str, timeout=3) -> str:
logger.info("It's your turn, please type in your response. You may also refer to the context below")
rsp = input(msg)
diff --git a/metagpt/provider/llm_provider_registry.py b/metagpt/provider/llm_provider_registry.py
index 2b3ef93a3..df89d36aa 100644
--- a/metagpt/provider/llm_provider_registry.py
+++ b/metagpt/provider/llm_provider_registry.py
@@ -5,7 +5,8 @@
@Author : alexanderwu
@File : llm_provider_registry.py
"""
-from metagpt.config import LLMProviderEnum
+from metagpt.configs.llm_config import LLMConfig, LLMType
+from metagpt.provider.base_llm import BaseLLM
class LLMProviderRegistry:
@@ -15,13 +16,9 @@ class LLMProviderRegistry:
def register(self, key, provider_cls):
self.providers[key] = provider_cls
- def get_provider(self, enum: LLMProviderEnum):
+ def get_provider(self, enum: LLMType):
"""get provider instance according to the enum"""
- return self.providers[enum]()
-
-
-# Registry instance
-LLM_REGISTRY = LLMProviderRegistry()
+ return self.providers[enum]
def register_provider(key):
@@ -32,3 +29,12 @@ def register_provider(key):
return cls
return decorator
+
+
+def create_llm_instance(config: LLMConfig) -> BaseLLM:
+ """get the default llm provider"""
+ return LLM_REGISTRY.get_provider(config.api_type)(config)
+
+
+# Registry instance
+LLM_REGISTRY = LLMProviderRegistry()
diff --git a/metagpt/provider/metagpt_api.py b/metagpt/provider/metagpt_api.py
index 69aa7f305..4956746dc 100644
--- a/metagpt/provider/metagpt_api.py
+++ b/metagpt/provider/metagpt_api.py
@@ -5,12 +5,11 @@
@File : metagpt_api.py
@Desc : MetaGPT LLM provider.
"""
-from metagpt.config import LLMProviderEnum
+from metagpt.configs.llm_config import LLMType
from metagpt.provider import OpenAILLM
from metagpt.provider.llm_provider_registry import register_provider
-@register_provider(LLMProviderEnum.METAGPT)
+@register_provider(LLMType.METAGPT)
class MetaGPTLLM(OpenAILLM):
- def __init__(self):
- super().__init__()
+ pass
diff --git a/metagpt/provider/ollama_api.py b/metagpt/provider/ollama_api.py
index 25086737f..c9103b018 100644
--- a/metagpt/provider/ollama_api.py
+++ b/metagpt/provider/ollama_api.py
@@ -13,48 +13,34 @@ from tenacity import (
wait_random_exponential,
)
-from metagpt.config import CONFIG, LLMProviderEnum
+from metagpt.configs.llm_config import LLMConfig, LLMType
from metagpt.const import LLM_API_TIMEOUT
from metagpt.logs import log_llm_stream, logger
from metagpt.provider.base_llm import BaseLLM
from metagpt.provider.general_api_requestor import GeneralAPIRequestor
from metagpt.provider.llm_provider_registry import register_provider
from metagpt.provider.openai_api import log_and_reraise
-from metagpt.utils.cost_manager import CostManager
+from metagpt.utils.cost_manager import TokenCostManager
-class OllamaCostManager(CostManager):
- def update_cost(self, prompt_tokens, completion_tokens, model):
- """
- Update the total cost, prompt tokens, and completion tokens.
- """
- self.total_prompt_tokens += prompt_tokens
- self.total_completion_tokens += completion_tokens
- max_budget = CONFIG.max_budget if CONFIG.max_budget else CONFIG.cost_manager.max_budget
- logger.info(
- f"Max budget: ${max_budget:.3f} | "
- f"prompt_tokens: {prompt_tokens}, completion_tokens: {completion_tokens}"
- )
- CONFIG.total_cost = self.total_cost
-
-
-@register_provider(LLMProviderEnum.OLLAMA)
+@register_provider(LLMType.OLLAMA)
class OllamaLLM(BaseLLM):
"""
Refs to `https://github.com/jmorganca/ollama/blob/main/docs/api.md#generate-a-chat-completion`
"""
- def __init__(self):
- self.__init_ollama(CONFIG)
- self.client = GeneralAPIRequestor(base_url=CONFIG.ollama_api_base)
+ def __init__(self, config: LLMConfig):
+ self.__init_ollama(config)
+ self.client = GeneralAPIRequestor(base_url=config.base_url)
+ self.config = config
self.suffix_url = "/chat"
self.http_method = "post"
self.use_system_prompt = False
- self._cost_manager = OllamaCostManager()
+ self._cost_manager = TokenCostManager()
- def __init_ollama(self, config: CONFIG):
- assert config.ollama_api_base
- self.model = config.ollama_api_model
+ def __init_ollama(self, config: LLMConfig):
+ assert config.base_url, "ollama base url is required!"
+ self.model = config.model
def _const_kwargs(self, messages: list[dict], stream: bool = False) -> dict:
kwargs = {"model": self.model, "messages": messages, "options": {"temperature": 0.3}, "stream": stream}
@@ -62,7 +48,7 @@ class OllamaLLM(BaseLLM):
def _update_costs(self, usage: dict):
"""update each request's token cost"""
- if CONFIG.calc_usage:
+ if self.config.calc_usage:
try:
prompt_tokens = int(usage.get("prompt_tokens", 0))
completion_tokens = int(usage.get("completion_tokens", 0))
diff --git a/metagpt/provider/open_llm_api.py b/metagpt/provider/open_llm_api.py
index b0c484f5a..a29b263a4 100644
--- a/metagpt/provider/open_llm_api.py
+++ b/metagpt/provider/open_llm_api.py
@@ -4,56 +4,27 @@
from openai.types import CompletionUsage
-from metagpt.config import CONFIG, Config, LLMProviderEnum
+from metagpt.configs.llm_config import LLMConfig, LLMType
from metagpt.logs import logger
from metagpt.provider.llm_provider_registry import register_provider
from metagpt.provider.openai_api import OpenAILLM
-from metagpt.utils.cost_manager import CostManager, Costs
+from metagpt.utils.cost_manager import Costs, TokenCostManager
from metagpt.utils.token_counter import count_message_tokens, count_string_tokens
-class OpenLLMCostManager(CostManager):
- """open llm model is self-host, it's free and without cost"""
-
- def update_cost(self, prompt_tokens, completion_tokens, model):
- """
- Update the total cost, prompt tokens, and completion tokens.
-
- Args:
- prompt_tokens (int): The number of tokens used in the prompt.
- completion_tokens (int): The number of tokens used in the completion.
- model (str): The model used for the API call.
- """
- self.total_prompt_tokens += prompt_tokens
- self.total_completion_tokens += completion_tokens
- max_budget = CONFIG.max_budget if CONFIG.max_budget else CONFIG.cost_manager.max_budget
- logger.info(
- f"Max budget: ${max_budget:.3f} | reference "
- f"prompt_tokens: {prompt_tokens}, completion_tokens: {completion_tokens}"
- )
-
-
-@register_provider(LLMProviderEnum.OPEN_LLM)
+@register_provider(LLMType.OPEN_LLM)
class OpenLLM(OpenAILLM):
- def __init__(self):
- self.config: Config = CONFIG
- self.__init_openllm()
- self.auto_max_tokens = False
- self._cost_manager = OpenLLMCostManager()
-
- def __init_openllm(self):
- self.is_azure = False
- self.rpm = int(self.config.get("RPM", 10))
- self._init_client()
- self.model = self.config.open_llm_api_model # `self.model` should after `_make_client` to rewrite it
+ def __init__(self, config: LLMConfig):
+ super().__init__(config)
+ self._cost_manager = TokenCostManager()
def _make_client_kwargs(self) -> dict:
- kwargs = dict(api_key="sk-xxx", base_url=self.config.open_llm_api_base)
+ kwargs = dict(api_key="sk-xxx", base_url=self.config.base_url)
return kwargs
def _calc_usage(self, messages: list[dict], rsp: str) -> CompletionUsage:
usage = CompletionUsage(prompt_tokens=0, completion_tokens=0, total_tokens=0)
- if not CONFIG.calc_usage:
+ if not self.config.calc_usage:
return usage
try:
diff --git a/metagpt/provider/openai_api.py b/metagpt/provider/openai_api.py
index 386c36c22..206701efd 100644
--- a/metagpt/provider/openai_api.py
+++ b/metagpt/provider/openai_api.py
@@ -3,15 +3,13 @@
@Time : 2023/5/5 23:08
@Author : alexanderwu
@File : openai.py
-@Modified By: mashenquan, 2023/8/20. Remove global configuration `CONFIG`, enable configuration support for isolation;
- Change cost control from global to company level.
@Modified By: mashenquan, 2023/11/21. Fix bug: ReadTimeout.
@Modified By: mashenquan, 2023/12/1. Fix bug: Unclosed connection caused by openai 0.x.
"""
import json
import re
-from typing import AsyncIterator, Union
+from typing import AsyncIterator, Optional, Union
from openai import APIConnectionError, AsyncOpenAI, AsyncStream
from openai._base_client import AsyncHttpxClientWrapper
@@ -25,14 +23,14 @@ from tenacity import (
wait_random_exponential,
)
-from metagpt.config import CONFIG, Config, LLMProviderEnum
+from metagpt.configs.llm_config import LLMConfig, LLMType
from metagpt.logs import log_llm_stream, logger
from metagpt.provider.base_llm import BaseLLM
from metagpt.provider.constant import GENERAL_FUNCTION_SCHEMA
from metagpt.provider.llm_provider_registry import register_provider
from metagpt.schema import Message
from metagpt.utils.common import CodeParser
-from metagpt.utils.cost_manager import Costs
+from metagpt.utils.cost_manager import CostManager, Costs
from metagpt.utils.exceptions import handle_exception
from metagpt.utils.token_counter import (
count_message_tokens,
@@ -52,18 +50,19 @@ See FAQ 5.8
raise retry_state.outcome.exception()
-@register_provider(LLMProviderEnum.OPENAI)
+@register_provider(LLMType.OPENAI)
class OpenAILLM(BaseLLM):
"""Check https://platform.openai.com/examples for examples"""
- def __init__(self):
- self.config: Config = CONFIG
- self._init_openai()
+ def __init__(self, config: LLMConfig):
+ self.config = config
+ self._init_model()
self._init_client()
self.auto_max_tokens = False
+ self.cost_manager: Optional[CostManager] = None
- def _init_openai(self):
- self.model = self.config.OPENAI_API_MODEL # Used in _calc_usage & _cons_kwargs
+ def _init_model(self):
+ self.model = self.config.model # Used in _calc_usage & _cons_kwargs
def _init_client(self):
"""https://github.com/openai/openai-python#async-usage"""
@@ -71,7 +70,7 @@ class OpenAILLM(BaseLLM):
self.aclient = AsyncOpenAI(**kwargs)
def _make_client_kwargs(self) -> dict:
- kwargs = {"api_key": self.config.openai_api_key, "base_url": self.config.openai_base_url}
+ kwargs = {"api_key": self.config.api_key, "base_url": self.config.base_url}
# to use proxy, openai v1 needs http_client
if proxy_params := self._get_proxy_params():
@@ -81,10 +80,10 @@ class OpenAILLM(BaseLLM):
def _get_proxy_params(self) -> dict:
params = {}
- if self.config.openai_proxy:
- params = {"proxies": self.config.openai_proxy}
- if self.config.openai_base_url:
- params["base_url"] = self.config.openai_base_url
+ if self.config.proxy:
+ params = {"proxies": self.config.proxy}
+ if self.config.base_url:
+ params["base_url"] = self.config.base_url
return params
@@ -105,7 +104,7 @@ class OpenAILLM(BaseLLM):
"stop": None,
"temperature": 0.3,
"model": self.model,
- "timeout": max(CONFIG.timeout, timeout),
+ "timeout": max(self.config.timeout, timeout),
}
if extra_kwargs:
kwargs.update(extra_kwargs)
@@ -266,7 +265,7 @@ class OpenAILLM(BaseLLM):
def _calc_usage(self, messages: list[dict], rsp: str) -> CompletionUsage:
usage = CompletionUsage(prompt_tokens=0, completion_tokens=0, total_tokens=0)
- if not CONFIG.calc_usage:
+ if not self.config.calc_usage:
return usage
try:
@@ -279,18 +278,28 @@ class OpenAILLM(BaseLLM):
@handle_exception
def _update_costs(self, usage: CompletionUsage):
- if CONFIG.calc_usage and usage:
- CONFIG.cost_manager.update_cost(usage.prompt_tokens, usage.completion_tokens, self.model)
+ if self.config.calc_usage and usage and self.cost_manager:
+ self.cost_manager.update_cost(usage.prompt_tokens, usage.completion_tokens, self.model)
def get_costs(self) -> Costs:
- return CONFIG.cost_manager.get_costs()
+ if not self.cost_manager:
+ return Costs(0, 0, 0, 0)
+ return self.cost_manager.get_costs()
def _get_max_tokens(self, messages: list[dict]):
if not self.auto_max_tokens:
- return CONFIG.max_tokens_rsp
- return get_max_completion_tokens(messages, self.model, CONFIG.max_tokens_rsp)
+ return self.config.max_token
+ return get_max_completion_tokens(messages, self.model, self.config.max_tokens)
@handle_exception
async def amoderation(self, content: Union[str, list[str]]):
"""Moderate content."""
return await self.aclient.moderations.create(input=content)
+
+ async def atext_to_speech(self, **kwargs):
+ """text to speech"""
+ return await self.aclient.audio.speech.create(**kwargs)
+
+ async def aspeech_to_text(self, **kwargs):
+ """speech to text"""
+ return await self.aclient.audio.transcriptions.create(**kwargs)
diff --git a/metagpt/provider/spark_api.py b/metagpt/provider/spark_api.py
index ce889529a..5e89c26d5 100644
--- a/metagpt/provider/spark_api.py
+++ b/metagpt/provider/spark_api.py
@@ -16,29 +16,30 @@ from wsgiref.handlers import format_date_time
import websocket # 使用websocket_client
-from metagpt.config import CONFIG, LLMProviderEnum
+from metagpt.configs.llm_config import LLMConfig, LLMType
from metagpt.logs import logger
from metagpt.provider.base_llm import BaseLLM
from metagpt.provider.llm_provider_registry import register_provider
-@register_provider(LLMProviderEnum.SPARK)
+@register_provider(LLMType.SPARK)
class SparkLLM(BaseLLM):
- def __init__(self):
- logger.warning("当前方法无法支持异步运行。当你使用acompletion时,并不能并行访问。")
+ def __init__(self, config: LLMConfig):
+ self.config = config
+ logger.warning("SparkLLM:当前方法无法支持异步运行。当你使用acompletion时,并不能并行访问。")
def get_choice_text(self, rsp: dict) -> str:
return rsp["payload"]["choices"]["text"][-1]["content"]
async def acompletion_text(self, messages: list[dict], stream=False, timeout: int = 3) -> str:
# 不支持
- logger.error("该功能禁用。")
- w = GetMessageFromWeb(messages)
+ # logger.warning("当前方法无法支持异步运行。当你使用acompletion时,并不能并行访问。")
+ w = GetMessageFromWeb(messages, self.config)
return w.run()
async def acompletion(self, messages: list[dict], timeout=3):
# 不支持异步
- w = GetMessageFromWeb(messages)
+ w = GetMessageFromWeb(messages, self.config)
return w.run()
@@ -89,14 +90,14 @@ class GetMessageFromWeb:
# 此处打印出建立连接时候的url,参考本demo的时候可取消上方打印的注释,比对相同参数时生成的url与自己代码生成的url是否一致
return url
- def __init__(self, text):
+ def __init__(self, text, config: LLMConfig):
self.text = text
self.ret = ""
- self.spark_appid = CONFIG.spark_appid
- self.spark_api_secret = CONFIG.spark_api_secret
- self.spark_api_key = CONFIG.spark_api_key
- self.domain = CONFIG.domain
- self.spark_url = CONFIG.spark_url
+ self.spark_appid = config.app_id
+ self.spark_api_secret = config.api_secret
+ self.spark_api_key = config.api_key
+ self.domain = config.domain
+ self.spark_url = config.base_url
def on_message(self, ws, message):
data = json.loads(message)
diff --git a/metagpt/provider/zhipuai/async_sse_client.py b/metagpt/provider/zhipuai/async_sse_client.py
index d7168202a..054865652 100644
--- a/metagpt/provider/zhipuai/async_sse_client.py
+++ b/metagpt/provider/zhipuai/async_sse_client.py
@@ -1,75 +1,31 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Desc : async_sse_client to make keep the use of Event to access response
-# refs to `https://github.com/zhipuai/zhipuai-sdk-python/blob/main/zhipuai/utils/sse_client.py`
+# refs to `zhipuai/core/_sse_client.py`
-from zhipuai.utils.sse_client import _FIELD_SEPARATOR, Event, SSEClient
+import json
+from typing import Any, Iterator
-class AsyncSSEClient(SSEClient):
- async def _aread(self):
- data = b""
+class AsyncSSEClient(object):
+ def __init__(self, event_source: Iterator[Any]):
+ self._event_source = event_source
+
+ async def stream(self) -> dict:
+ if isinstance(self._event_source, bytes):
+ raise RuntimeError(
+ f"Request failed, msg: {self._event_source.decode('utf-8')}, please ref to `https://open.bigmodel.cn/dev/api#error-code-v3`"
+ )
async for chunk in self._event_source:
- for line in chunk.splitlines(True):
- data += line
- if data.endswith((b"\r\r", b"\n\n", b"\r\n\r\n")):
- yield data
- data = b""
- if data:
- yield data
+ line = chunk.decode("utf-8")
+ if line.startswith(":") or not line:
+ return
- async def async_events(self):
- async for chunk in self._aread():
- event = Event()
- # Split before decoding so splitlines() only uses \r and \n
- for line in chunk.splitlines():
- # Decode the line.
- line = line.decode(self._char_enc)
-
- # Lines starting with a separator are comments and are to be
- # ignored.
- if not line.strip() or line.startswith(_FIELD_SEPARATOR):
- continue
-
- data = line.split(_FIELD_SEPARATOR, 1)
- field = data[0]
-
- # Ignore unknown fields.
- if field not in event.__dict__:
- self._logger.debug("Saw invalid field %s while parsing " "Server Side Event", field)
- continue
-
- if len(data) > 1:
- # From the spec:
- # "If value starts with a single U+0020 SPACE character,
- # remove it from value."
- if data[1].startswith(" "):
- value = data[1][1:]
- else:
- value = data[1]
- else:
- # If no value is present after the separator,
- # assume an empty value.
- value = ""
-
- # The data field may come over multiple lines and their values
- # are concatenated with each other.
- if field == "data":
- event.__dict__[field] += value + "\n"
- else:
- event.__dict__[field] = value
-
- # Events with no data are not dispatched.
- if not event.data:
- continue
-
- # If the data field ends with a newline, remove it.
- if event.data.endswith("\n"):
- event.data = event.data[0:-1]
-
- # Empty event names default to 'message'
- event.event = event.event or "message"
-
- # Dispatch the event
- self._logger.debug("Dispatching %s...", event)
- yield event
+ field, _p, value = line.partition(":")
+ if value.startswith(" "):
+ value = value[1:]
+ if field == "data":
+ if value.startswith("[DONE]"):
+ break
+ data = json.loads(value)
+ yield data
diff --git a/metagpt/provider/zhipuai/zhipu_model_api.py b/metagpt/provider/zhipuai/zhipu_model_api.py
index 16d4102d4..a7d49623a 100644
--- a/metagpt/provider/zhipuai/zhipu_model_api.py
+++ b/metagpt/provider/zhipuai/zhipu_model_api.py
@@ -4,46 +4,27 @@
import json
-import zhipuai
-from zhipuai.model_api.api import InvokeType, ModelAPI
-from zhipuai.utils.http_client import headers as zhipuai_default_headers
+from zhipuai import ZhipuAI
+from zhipuai.core._http_client import ZHIPUAI_DEFAULT_TIMEOUT
from metagpt.provider.general_api_requestor import GeneralAPIRequestor
from metagpt.provider.zhipuai.async_sse_client import AsyncSSEClient
-class ZhiPuModelAPI(ModelAPI):
- @classmethod
- def get_header(cls) -> dict:
- token = cls._generate_token()
- zhipuai_default_headers.update({"Authorization": token})
- return zhipuai_default_headers
-
- @classmethod
- def get_sse_header(cls) -> dict:
- token = cls._generate_token()
- headers = {"Authorization": token}
- return headers
-
- @classmethod
- def split_zhipu_api_url(cls, invoke_type: InvokeType, kwargs):
+class ZhiPuModelAPI(ZhipuAI):
+ def split_zhipu_api_url(self):
# use this method to prevent zhipu api upgrading to different version.
# and follow the GeneralAPIRequestor implemented based on openai sdk
- zhipu_api_url = cls._build_api_url(kwargs, invoke_type)
- """
- example:
- zhipu_api_url: https://open.bigmodel.cn/api/paas/v3/model-api/{model}/{invoke_method}
- """
+ zhipu_api_url = "https://open.bigmodel.cn/api/paas/v4/chat/completions"
arr = zhipu_api_url.split("/api/")
- # ("https://open.bigmodel.cn/api" , "/paas/v3/model-api/chatglm_turbo/invoke")
+ # ("https://open.bigmodel.cn/api" , "/paas/v4/chat/completions")
return f"{arr[0]}/api", f"/{arr[1]}"
- @classmethod
- async def arequest(cls, invoke_type: InvokeType, stream: bool, method: str, headers: dict, kwargs):
+ async def arequest(self, stream: bool, method: str, headers: dict, kwargs):
# TODO to make the async request to be more generic for models in http mode.
assert method in ["post", "get"]
- base_url, url = cls.split_zhipu_api_url(invoke_type, kwargs)
+ base_url, url = self.split_zhipu_api_url()
requester = GeneralAPIRequestor(base_url=base_url)
result, _, api_key = await requester.arequest(
method=method,
@@ -51,25 +32,23 @@ class ZhiPuModelAPI(ModelAPI):
headers=headers,
stream=stream,
params=kwargs,
- request_timeout=zhipuai.api_timeout_seconds,
+ request_timeout=ZHIPUAI_DEFAULT_TIMEOUT.read,
)
return result
- @classmethod
- async def ainvoke(cls, **kwargs) -> dict:
+ async def acreate(self, **kwargs) -> dict:
"""async invoke different from raw method `async_invoke` which get the final result by task_id"""
- headers = cls.get_header()
- resp = await cls.arequest(
- invoke_type=InvokeType.SYNC, stream=False, method="post", headers=headers, kwargs=kwargs
- )
+ headers = self._default_headers
+ resp = await self.arequest(stream=False, method="post", headers=headers, kwargs=kwargs)
resp = resp.decode("utf-8")
resp = json.loads(resp)
+ if "error" in resp:
+ raise RuntimeError(
+ f"Request failed, msg: {resp}, please ref to `https://open.bigmodel.cn/dev/api#error-code-v3`"
+ )
return resp
- @classmethod
- async def asse_invoke(cls, **kwargs) -> AsyncSSEClient:
+ async def acreate_stream(self, **kwargs) -> AsyncSSEClient:
"""async sse_invoke"""
- headers = cls.get_sse_header()
- return AsyncSSEClient(
- await cls.arequest(invoke_type=InvokeType.SSE, stream=True, method="post", headers=headers, kwargs=kwargs)
- )
+ headers = self._default_headers
+ return AsyncSSEClient(await self.arequest(stream=True, method="post", headers=headers, kwargs=kwargs))
diff --git a/metagpt/provider/zhipuai_api.py b/metagpt/provider/zhipuai_api.py
index e1ccf0de5..9108a1fba 100644
--- a/metagpt/provider/zhipuai_api.py
+++ b/metagpt/provider/zhipuai_api.py
@@ -2,7 +2,6 @@
# -*- coding: utf-8 -*-
# @Desc : zhipuai LLM from https://open.bigmodel.cn/dev/api#sdk
-import json
from enum import Enum
import openai
@@ -16,7 +15,7 @@ from tenacity import (
wait_random_exponential,
)
-from metagpt.config import CONFIG, LLMProviderEnum
+from metagpt.configs.llm_config import LLMConfig, LLMType
from metagpt.logs import log_llm_stream, logger
from metagpt.provider.base_llm import BaseLLM
from metagpt.provider.llm_provider_registry import register_provider
@@ -31,57 +30,52 @@ class ZhiPuEvent(Enum):
FINISH = "finish"
-@register_provider(LLMProviderEnum.ZHIPUAI)
+@register_provider(LLMType.ZHIPUAI)
class ZhiPuAILLM(BaseLLM):
"""
Refs to `https://open.bigmodel.cn/dev/api#chatglm_turbo`
- From now, there is only one model named `chatglm_turbo`
+ From now, support glm-3-turbo、glm-4, and also system_prompt.
"""
- def __init__(self):
- self.__init_zhipuai(CONFIG)
+ def __init__(self, config: LLMConfig):
+ self.__init_zhipuai(config)
self.llm = ZhiPuModelAPI
self.model = "chatglm_turbo" # so far only one model, just use it
self.use_system_prompt: bool = False # zhipuai has no system prompt when use api
+ self.config = config
- def __init_zhipuai(self, config: CONFIG):
- assert config.zhipuai_api_key
- zhipuai.api_key = config.zhipuai_api_key
+ def __init_zhipuai(self, config: LLMConfig):
+ assert config.api_key
+ zhipuai.api_key = config.api_key
# due to use openai sdk, set the api_key but it will't be used.
# openai.api_key = zhipuai.api_key # due to use openai sdk, set the api_key but it will't be used.
- if config.openai_proxy:
+ if config.proxy:
# FIXME: openai v1.x sdk has no proxy support
- openai.proxy = config.openai_proxy
+ openai.proxy = config.proxy
- def _const_kwargs(self, messages: list[dict]) -> dict:
- kwargs = {"model": self.model, "prompt": messages, "temperature": 0.3}
+ def _const_kwargs(self, messages: list[dict], stream: bool = False) -> dict:
+ kwargs = {"model": self.model, "messages": messages, "stream": stream, "temperature": 0.3}
return kwargs
def _update_costs(self, usage: dict):
"""update each request's token cost"""
- if CONFIG.calc_usage:
+ if self.config.calc_usage:
try:
prompt_tokens = int(usage.get("prompt_tokens", 0))
completion_tokens = int(usage.get("completion_tokens", 0))
- CONFIG.cost_manager.update_cost(prompt_tokens, completion_tokens, self.model)
+ self.config.cost_manager.update_cost(prompt_tokens, completion_tokens, self.model)
except Exception as e:
logger.error(f"zhipuai updats costs failed! exp: {e}")
- def get_choice_text(self, resp: dict) -> str:
- """get the first text of choice from llm response"""
- assist_msg = resp.get("data", {}).get("choices", [{"role": "error"}])[-1]
- assert assist_msg["role"] == "assistant"
- return assist_msg.get("content")
-
def completion(self, messages: list[dict], timeout=3) -> dict:
- resp = self.llm.invoke(**self._const_kwargs(messages))
- usage = resp.get("data").get("usage")
+ resp = self.llm.chat.completions.create(**self._const_kwargs(messages))
+ usage = resp.usage.model_dump()
self._update_costs(usage)
- return resp
+ return resp.model_dump()
async def _achat_completion(self, messages: list[dict], timeout=3) -> dict:
- resp = await self.llm.ainvoke(**self._const_kwargs(messages))
- usage = resp.get("data").get("usage")
+ resp = await self.llm.acreate(**self._const_kwargs(messages))
+ usage = resp.get("usage", {})
self._update_costs(usage)
return resp
@@ -89,35 +83,18 @@ class ZhiPuAILLM(BaseLLM):
return await self._achat_completion(messages, timeout=timeout)
async def _achat_completion_stream(self, messages: list[dict], timeout=3) -> str:
- response = await self.llm.asse_invoke(**self._const_kwargs(messages))
+ response = await self.llm.acreate_stream(**self._const_kwargs(messages, stream=True))
collected_content = []
usage = {}
- async for event in response.async_events():
- if event.event == ZhiPuEvent.ADD.value:
- content = event.data
+ async for chunk in response.stream():
+ finish_reason = chunk.get("choices")[0].get("finish_reason")
+ if finish_reason == "stop":
+ usage = chunk.get("usage", {})
+ else:
+ content = self.get_choice_delta_text(chunk)
collected_content.append(content)
log_llm_stream(content)
- elif event.event == ZhiPuEvent.ERROR.value or event.event == ZhiPuEvent.INTERRUPTED.value:
- content = event.data
- logger.error(f"event error: {content}", end="")
- elif event.event == ZhiPuEvent.FINISH.value:
- """
- event.meta
- {
- "task_status":"SUCCESS",
- "usage":{
- "completion_tokens":351,
- "prompt_tokens":595,
- "total_tokens":946
- },
- "task_id":"xx",
- "request_id":"xxx"
- }
- """
- meta = json.loads(event.meta)
- usage = meta.get("usage")
- else:
- print(f"zhipuapi else event: {event.data}", end="")
+
log_llm_stream("\n")
self._update_costs(usage)
diff --git a/metagpt/roles/architect.py b/metagpt/roles/architect.py
index c6ceaccb7..166f8cfd0 100644
--- a/metagpt/roles/architect.py
+++ b/metagpt/roles/architect.py
@@ -33,7 +33,7 @@ class Architect(Role):
def __init__(self, **kwargs) -> None:
super().__init__(**kwargs)
# Initialize actions specific to the Architect role
- self._init_actions([WriteDesign])
+ self.set_actions([WriteDesign])
# Set events or actions the Architect should watch or be aware of
self._watch({WritePRD})
diff --git a/metagpt/roles/assistant.py b/metagpt/roles/assistant.py
index 227578a63..2774bd9b6 100644
--- a/metagpt/roles/assistant.py
+++ b/metagpt/roles/assistant.py
@@ -22,7 +22,6 @@ from pydantic import Field
from metagpt.actions.skill_action import ArgumentsParingAction, SkillAction
from metagpt.actions.talk_action import TalkAction
-from metagpt.config import CONFIG
from metagpt.learn.skill_loader import SkillsDeclaration
from metagpt.logs import logger
from metagpt.memory.brain_memory import BrainMemory
@@ -48,7 +47,8 @@ class Assistant(Role):
def __init__(self, **kwargs):
super().__init__(**kwargs)
- self.constraints = self.constraints.format(language=kwargs.get("language") or CONFIG.language or "Chinese")
+ language = kwargs.get("language") or self.context.kwargs.language
+ self.constraints = self.constraints.format(language=language)
async def think(self) -> bool:
"""Everything will be done part by part."""
@@ -56,16 +56,16 @@ class Assistant(Role):
if not last_talk:
return False
if not self.skills:
- skill_path = Path(CONFIG.SKILL_PATH) if CONFIG.SKILL_PATH else None
+ skill_path = Path(self.context.kwargs.SKILL_PATH) if self.context.kwargs.SKILL_PATH else None
self.skills = await SkillsDeclaration.load(skill_yaml_file_name=skill_path)
prompt = ""
- skills = self.skills.get_skill_list()
+ skills = self.skills.get_skill_list(context=self.context)
for desc, name in skills.items():
prompt += f"If the text explicitly want you to {desc}, return `[SKILL]: {name}` brief and clear. For instance: [SKILL]: {name}\n"
prompt += 'Otherwise, return `[TALK]: {talk}` brief and clear. For instance: if {talk} is "xxxx" return [TALK]: xxxx\n\n'
prompt += f"Now what specific action is explicitly mentioned in the text: {last_talk}\n"
- rsp = await self.llm.aask(prompt, [])
+ rsp = await self.llm.aask(prompt, ["You are an action classifier"])
logger.info(f"THINK: {prompt}\n, THINK RESULT: {rsp}\n")
return await self._plan(rsp, last_talk=last_talk)
@@ -97,8 +97,8 @@ class Assistant(Role):
async def talk_handler(self, text, **kwargs) -> bool:
history = self.memory.history_text
text = kwargs.get("last_talk") or text
- self.rc.todo = TalkAction(
- context=text, knowledge=self.memory.get_knowledge(), history_summary=history, llm=self.llm, **kwargs
+ self.set_todo(
+ TalkAction(i_context=text, knowledge=self.memory.get_knowledge(), history_summary=history, llm=self.llm)
)
return True
@@ -108,11 +108,11 @@ class Assistant(Role):
if not skill:
logger.info(f"skill not found: {text}")
return await self.talk_handler(text=last_talk, **kwargs)
- action = ArgumentsParingAction(skill=skill, llm=self.llm, ask=last_talk, **kwargs)
+ action = ArgumentsParingAction(skill=skill, llm=self.llm, ask=last_talk)
await action.run(**kwargs)
if action.args is None:
return await self.talk_handler(text=last_talk, **kwargs)
- self.rc.todo = SkillAction(skill=skill, args=action.args, llm=self.llm, name=skill.name, desc=skill.description)
+ self.set_todo(SkillAction(skill=skill, args=action.args, llm=self.llm, name=skill.name, desc=skill.description))
return True
async def refine_memory(self) -> str:
diff --git a/metagpt/roles/code_interpreter.py b/metagpt/roles/code_interpreter.py
index 3991862d1..b4f9622d3 100644
--- a/metagpt/roles/code_interpreter.py
+++ b/metagpt/roles/code_interpreter.py
@@ -1,23 +1,16 @@
-from datetime import datetime
-
from pydantic import Field
from metagpt.actions.ask_review import ReviewConst
from metagpt.actions.execute_code import ExecutePyCode
from metagpt.actions.write_analysis_code import WriteCodeByGenerate, WriteCodeWithTools
-from metagpt.actions.write_code_steps import WriteCodeSteps
from metagpt.logs import logger
from metagpt.roles import Role
-from metagpt.roles.tool_maker import ToolMaker
from metagpt.schema import Message, Task, TaskResult
-from metagpt.utils.save_code import save_code_file
class CodeInterpreter(Role):
auto_run: bool = True
use_tools: bool = False
- make_udfs: bool = False # whether to save user-defined functions
- use_code_steps: bool = False
execute_code: ExecutePyCode = Field(default_factory=ExecutePyCode, exclude=True)
tools: list[str] = []
@@ -47,29 +40,12 @@ class CodeInterpreter(Role):
def working_memory(self):
return self.rc.working_memory
- async def _plan_and_act(self):
- rsp = await super()._plan_and_act()
-
- # save code using datetime.now or keywords related to the goal of your project (plan.goal).
- project_record = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
- save_code_file(name=project_record, code_context=self.execute_code.nb, file_format="ipynb")
-
- # make tools out of workable codes for future use
- if self.make_udfs:
- await self.make_tools()
-
- return rsp
-
async def _act_on_task(self, current_task: Task) -> TaskResult:
code, result, is_success = await self._write_and_exec_code()
task_result = TaskResult(code=code, result=result, is_success=is_success)
return task_result
async def _write_and_exec_code(self, max_retry: int = 3):
- self.planner.current_task.code_steps = (
- await WriteCodeSteps().run(self.planner.plan) if self.use_code_steps else ""
- )
-
counter = 0
success = False
@@ -97,7 +73,11 @@ class CodeInterpreter(Role):
if ReviewConst.CHANGE_WORD[0] in review:
counter = 0 # redo the task again with help of human suggestions
- return code["code"] if code["language"] != "markdown" else "", result, success
+ py_code = (
+ code["code"] if code.get("language") != "markdown" else ""
+ ) # use python code as final code; for markdown, return the rendered result instead of the code itself
+
+ return py_code, result, success
async def _write_code(self):
todo = WriteCodeByGenerate() if not self.use_tools else WriteCodeWithTools(selected_tools=self.tools)
@@ -108,12 +88,3 @@ class CodeInterpreter(Role):
code = await todo.run(context=context, plan=self.planner.plan, temperature=0.0)
return code, todo
-
- async def make_tools(self):
- """Make user-defined functions(udfs, aka tools) for pure generation code."""
- logger.info("Plan completed. Now start to make tools ...")
- tool_maker = ToolMaker()
- for task in self.planner.plan.get_finished_tasks():
- await tool_maker.make_tool(
- code=task.code, instruction=task.instruction, task_id=task.task_id, auto_run=self.auto_run
- )
diff --git a/metagpt/roles/engineer.py b/metagpt/roles/engineer.py
index e05e69cbb..40ade2110 100644
--- a/metagpt/roles/engineer.py
+++ b/metagpt/roles/engineer.py
@@ -20,23 +20,27 @@
from __future__ import annotations
import json
+import os
from collections import defaultdict
from pathlib import Path
from typing import Set
from metagpt.actions import Action, WriteCode, WriteCodeReview, WriteTasks
from metagpt.actions.fix_bug import FixBug
+from metagpt.actions.project_management_an import REFINED_TASK_LIST, TASK_LIST
from metagpt.actions.summarize_code import SummarizeCode
-from metagpt.config import CONFIG
+from metagpt.actions.write_code_plan_and_change_an import WriteCodePlanAndChange
from metagpt.const import (
- CODE_SUMMARIES_FILE_REPO,
- CODE_SUMMARIES_PDF_FILE_REPO,
+ CODE_PLAN_AND_CHANGE_FILE_REPO,
+ CODE_PLAN_AND_CHANGE_FILENAME,
+ REQUIREMENT_FILENAME,
SYSTEM_DESIGN_FILE_REPO,
TASK_FILE_REPO,
)
from metagpt.logs import logger
from metagpt.roles import Role
from metagpt.schema import (
+ CodePlanAndChangeContext,
CodeSummarizeContext,
CodingContext,
Document,
@@ -80,12 +84,13 @@ class Engineer(Role):
code_todos: list = []
summarize_todos: list = []
next_todo_action: str = ""
+ n_summarize: int = 0
def __init__(self, **kwargs) -> None:
super().__init__(**kwargs)
- self._init_actions([WriteCode])
- self._watch([WriteTasks, SummarizeCode, WriteCode, WriteCodeReview, FixBug])
+ self.set_actions([WriteCode])
+ self._watch([WriteTasks, SummarizeCode, WriteCode, WriteCodeReview, FixBug, WriteCodePlanAndChange])
self.code_todos = []
self.summarize_todos = []
self.next_todo_action = any_to_name(WriteCode)
@@ -93,11 +98,10 @@ class Engineer(Role):
@staticmethod
def _parse_tasks(task_msg: Document) -> list[str]:
m = json.loads(task_msg.content)
- return m.get("Task list")
+ return m.get(TASK_LIST.key) or m.get(REFINED_TASK_LIST.key)
async def _act_sp_with_cr(self, review=False) -> Set[str]:
changed_files = set()
- src_file_repo = CONFIG.git_repo.new_file_repository(CONFIG.src_workspace)
for todo in self.code_todos:
"""
# Select essential information from the historical data to reduce the length of the prompt (summarized from human experience):
@@ -109,12 +113,16 @@ class Engineer(Role):
coding_context = await todo.run()
# Code review
if review:
- action = WriteCodeReview(context=coding_context, llm=self.llm)
- self._init_action_system_message(action)
+ action = WriteCodeReview(i_context=coding_context, context=self.context, llm=self.llm)
+ self._init_action(action)
coding_context = await action.run()
- await src_file_repo.save(
- coding_context.filename,
- dependencies={coding_context.design_doc.root_relative_path, coding_context.task_doc.root_relative_path},
+
+ dependencies = {coding_context.design_doc.root_relative_path, coding_context.task_doc.root_relative_path}
+ if self.config.inc:
+ dependencies.add(os.path.join(CODE_PLAN_AND_CHANGE_FILE_REPO, CODE_PLAN_AND_CHANGE_FILENAME))
+ await self.project_repo.srcs.save(
+ filename=coding_context.filename,
+ dependencies=dependencies,
content=coding_context.code_doc.content,
)
msg = Message(
@@ -134,6 +142,9 @@ class Engineer(Role):
"""Determines the mode of action based on whether code review is used."""
if self.rc.todo is None:
return None
+ if isinstance(self.rc.todo, WriteCodePlanAndChange):
+ self.next_todo_action = any_to_name(WriteCode)
+ return await self._act_code_plan_and_change()
if isinstance(self.rc.todo, WriteCode):
self.next_todo_action = any_to_name(SummarizeCode)
return await self._act_write_code()
@@ -153,34 +164,32 @@ class Engineer(Role):
)
async def _act_summarize(self):
- code_summaries_file_repo = CONFIG.git_repo.new_file_repository(CODE_SUMMARIES_FILE_REPO)
- code_summaries_pdf_file_repo = CONFIG.git_repo.new_file_repository(CODE_SUMMARIES_PDF_FILE_REPO)
tasks = []
- src_relative_path = CONFIG.src_workspace.relative_to(CONFIG.git_repo.workdir)
for todo in self.summarize_todos:
summary = await todo.run()
- summary_filename = Path(todo.context.design_filename).with_suffix(".md").name
- dependencies = {todo.context.design_filename, todo.context.task_filename}
- for filename in todo.context.codes_filenames:
- rpath = src_relative_path / filename
+ summary_filename = Path(todo.i_context.design_filename).with_suffix(".md").name
+ dependencies = {todo.i_context.design_filename, todo.i_context.task_filename}
+ for filename in todo.i_context.codes_filenames:
+ rpath = self.project_repo.src_relative_path / filename
dependencies.add(str(rpath))
- await code_summaries_pdf_file_repo.save(
+ await self.project_repo.resources.code_summary.save(
filename=summary_filename, content=summary, dependencies=dependencies
)
is_pass, reason = await self._is_pass(summary)
if not is_pass:
- todo.context.reason = reason
- tasks.append(todo.context.dict())
- await code_summaries_file_repo.save(
- filename=Path(todo.context.design_filename).name,
- content=todo.context.model_dump_json(),
+ todo.i_context.reason = reason
+ tasks.append(todo.i_context.model_dump())
+
+ await self.project_repo.docs.code_summary.save(
+ filename=Path(todo.i_context.design_filename).name,
+ content=todo.i_context.model_dump_json(),
dependencies=dependencies,
)
else:
- await code_summaries_file_repo.delete(filename=Path(todo.context.design_filename).name)
+ await self.project_repo.docs.code_summary.delete(filename=Path(todo.i_context.design_filename).name)
- logger.info(f"--max-auto-summarize-code={CONFIG.max_auto_summarize_code}")
- if not tasks or CONFIG.max_auto_summarize_code == 0:
+ logger.info(f"--max-auto-summarize-code={self.config.max_auto_summarize_code}")
+ if not tasks or self.config.max_auto_summarize_code == 0:
return Message(
content="",
role=self.profile,
@@ -190,11 +199,39 @@ class Engineer(Role):
)
# The maximum number of times the 'SummarizeCode' action is automatically invoked, with -1 indicating unlimited.
# This parameter is used for debugging the workflow.
- CONFIG.max_auto_summarize_code -= 1 if CONFIG.max_auto_summarize_code > 0 else 0
+ self.n_summarize += 1 if self.config.max_auto_summarize_code > self.n_summarize else 0
return Message(
content=json.dumps(tasks), role=self.profile, cause_by=SummarizeCode, send_to=self, sent_from=self
)
+ async def _act_code_plan_and_change(self):
+ """Write code plan and change that guides subsequent WriteCode and WriteCodeReview"""
+ logger.info("Writing code plan and change..")
+ node = await self.rc.todo.run()
+ code_plan_and_change = node.instruct_content.model_dump_json()
+ dependencies = {
+ REQUIREMENT_FILENAME,
+ self.rc.todo.i_context.prd_filename,
+ self.rc.todo.i_context.design_filename,
+ self.rc.todo.i_context.task_filename,
+ }
+ await self.project_repo.docs.code_plan_and_change.save(
+ filename=self.rc.todo.i_context.filename, content=code_plan_and_change, dependencies=dependencies
+ )
+ await self.project_repo.resources.code_plan_and_change.save(
+ filename=Path(self.rc.todo.i_context.filename).with_suffix(".md").name,
+ content=node.content,
+ dependencies=dependencies,
+ )
+
+ return Message(
+ content=code_plan_and_change,
+ role=self.profile,
+ cause_by=WriteCodePlanAndChange,
+ send_to=self,
+ sent_from=self,
+ )
+
async def _is_pass(self, summary) -> (str, str):
rsp = await self.llm.aask(msg=IS_PASS_PROMPT.format(context=summary), stream=False)
logger.info(rsp)
@@ -203,13 +240,18 @@ class Engineer(Role):
return False, rsp
async def _think(self) -> Action | None:
- if not CONFIG.src_workspace:
- CONFIG.src_workspace = CONFIG.git_repo.workdir / CONFIG.git_repo.workdir.name
- write_code_filters = any_to_str_set([WriteTasks, SummarizeCode, FixBug])
+ if not self.src_workspace:
+ self.src_workspace = self.git_repo.workdir / self.git_repo.workdir.name
+ write_plan_and_change_filters = any_to_str_set([WriteTasks])
+ write_code_filters = any_to_str_set([WriteTasks, WriteCodePlanAndChange, SummarizeCode, FixBug])
summarize_code_filters = any_to_str_set([WriteCode, WriteCodeReview])
if not self.rc.news:
return None
msg = self.rc.news[0]
+ if self.config.inc and msg.cause_by in write_plan_and_change_filters:
+ logger.debug(f"TODO WriteCodePlanAndChange:{msg.model_dump_json()}")
+ await self._new_code_plan_and_change_action()
+ return self.rc.todo
if msg.cause_by in write_code_filters:
logger.debug(f"TODO WriteCode:{msg.model_dump_json()}")
await self._new_code_actions(bug_fix=msg.cause_by == any_to_str(FixBug))
@@ -220,60 +262,54 @@ class Engineer(Role):
return self.rc.todo
return None
- @staticmethod
- async def _new_coding_context(
- filename, src_file_repo, task_file_repo, design_file_repo, dependency
- ) -> CodingContext:
- old_code_doc = await src_file_repo.get(filename)
+ async def _new_coding_context(self, filename, dependency) -> CodingContext:
+ old_code_doc = await self.project_repo.srcs.get(filename)
if not old_code_doc:
- old_code_doc = Document(root_path=str(src_file_repo.root_path), filename=filename, content="")
+ old_code_doc = Document(root_path=str(self.project_repo.src_relative_path), filename=filename, content="")
dependencies = {Path(i) for i in await dependency.get(old_code_doc.root_relative_path)}
task_doc = None
design_doc = None
for i in dependencies:
if str(i.parent) == TASK_FILE_REPO:
- task_doc = await task_file_repo.get(i.name)
+ task_doc = await self.project_repo.docs.task.get(i.name)
elif str(i.parent) == SYSTEM_DESIGN_FILE_REPO:
- design_doc = await design_file_repo.get(i.name)
+ design_doc = await self.project_repo.docs.system_design.get(i.name)
if not task_doc or not design_doc:
logger.error(f'Detected source code "{filename}" from an unknown origin.')
raise ValueError(f'Detected source code "{filename}" from an unknown origin.')
context = CodingContext(filename=filename, design_doc=design_doc, task_doc=task_doc, code_doc=old_code_doc)
return context
- @staticmethod
- async def _new_coding_doc(filename, src_file_repo, task_file_repo, design_file_repo, dependency):
- context = await Engineer._new_coding_context(
- filename, src_file_repo, task_file_repo, design_file_repo, dependency
- )
+ async def _new_coding_doc(self, filename, dependency):
+ context = await self._new_coding_context(filename, dependency)
coding_doc = Document(
- root_path=str(src_file_repo.root_path), filename=filename, content=context.model_dump_json()
+ root_path=str(self.project_repo.src_relative_path), filename=filename, content=context.model_dump_json()
)
return coding_doc
async def _new_code_actions(self, bug_fix=False):
# Prepare file repos
- src_file_repo = CONFIG.git_repo.new_file_repository(CONFIG.src_workspace)
- changed_src_files = src_file_repo.all_files if bug_fix else src_file_repo.changed_files
- task_file_repo = CONFIG.git_repo.new_file_repository(TASK_FILE_REPO)
- changed_task_files = task_file_repo.changed_files
- design_file_repo = CONFIG.git_repo.new_file_repository(SYSTEM_DESIGN_FILE_REPO)
-
+ changed_src_files = self.project_repo.srcs.all_files if bug_fix else self.project_repo.srcs.changed_files
+ changed_task_files = self.project_repo.docs.task.changed_files
changed_files = Documents()
# Recode caused by upstream changes.
for filename in changed_task_files:
- design_doc = await design_file_repo.get(filename)
- task_doc = await task_file_repo.get(filename)
+ design_doc = await self.project_repo.docs.system_design.get(filename)
+ task_doc = await self.project_repo.docs.task.get(filename)
task_list = self._parse_tasks(task_doc)
for task_filename in task_list:
- old_code_doc = await src_file_repo.get(task_filename)
+ old_code_doc = await self.project_repo.srcs.get(task_filename)
if not old_code_doc:
- old_code_doc = Document(root_path=str(src_file_repo.root_path), filename=task_filename, content="")
+ old_code_doc = Document(
+ root_path=str(self.project_repo.src_relative_path), filename=task_filename, content=""
+ )
context = CodingContext(
filename=task_filename, design_doc=design_doc, task_doc=task_doc, code_doc=old_code_doc
)
coding_doc = Document(
- root_path=str(src_file_repo.root_path), filename=task_filename, content=context.model_dump_json()
+ root_path=str(self.project_repo.src_relative_path),
+ filename=task_filename,
+ content=context.model_dump_json(),
)
if task_filename in changed_files.docs:
logger.warning(
@@ -281,41 +317,44 @@ class Engineer(Role):
f"{changed_files.docs[task_filename].model_dump_json()}"
)
changed_files.docs[task_filename] = coding_doc
- self.code_todos = [WriteCode(context=i, llm=self.llm) for i in changed_files.docs.values()]
+ self.code_todos = [
+ WriteCode(i_context=i, context=self.context, llm=self.llm) for i in changed_files.docs.values()
+ ]
# Code directly modified by the user.
- dependency = await CONFIG.git_repo.get_dependency()
+ dependency = await self.git_repo.get_dependency()
for filename in changed_src_files:
if filename in changed_files.docs:
continue
- coding_doc = await self._new_coding_doc(
- filename=filename,
- src_file_repo=src_file_repo,
- task_file_repo=task_file_repo,
- design_file_repo=design_file_repo,
- dependency=dependency,
- )
+ coding_doc = await self._new_coding_doc(filename=filename, dependency=dependency)
changed_files.docs[filename] = coding_doc
- self.code_todos.append(WriteCode(context=coding_doc, llm=self.llm))
+ self.code_todos.append(WriteCode(i_context=coding_doc, context=self.context, llm=self.llm))
if self.code_todos:
- self.rc.todo = self.code_todos[0]
+ self.set_todo(self.code_todos[0])
async def _new_summarize_actions(self):
- src_file_repo = CONFIG.git_repo.new_file_repository(CONFIG.src_workspace)
- src_files = src_file_repo.all_files
+ src_files = self.project_repo.srcs.all_files
# Generate a SummarizeCode action for each pair of (system_design_doc, task_doc).
summarizations = defaultdict(list)
for filename in src_files:
- dependencies = await src_file_repo.get_dependency(filename=filename)
- ctx = CodeSummarizeContext.loads(filenames=dependencies)
+ dependencies = await self.project_repo.srcs.get_dependency(filename=filename)
+ ctx = CodeSummarizeContext.loads(filenames=list(dependencies))
summarizations[ctx].append(filename)
for ctx, filenames in summarizations.items():
ctx.codes_filenames = filenames
- self.summarize_todos.append(SummarizeCode(context=ctx, llm=self.llm))
+ self.summarize_todos.append(SummarizeCode(i_context=ctx, context=self.context, llm=self.llm))
if self.summarize_todos:
- self.rc.todo = self.summarize_todos[0]
+ self.set_todo(self.summarize_todos[0])
+
+ async def _new_code_plan_and_change_action(self):
+ """Create a WriteCodePlanAndChange action for subsequent to-do actions."""
+ files = self.project_repo.all_files
+ requirement_doc = await self.project_repo.docs.get(REQUIREMENT_FILENAME)
+ requirement = requirement_doc.content if requirement_doc else ""
+ code_plan_and_change_ctx = CodePlanAndChangeContext.loads(files, requirement=requirement)
+ self.rc.todo = WriteCodePlanAndChange(i_context=code_plan_and_change_ctx, context=self.context, llm=self.llm)
@property
- def todo(self) -> str:
+ def action_description(self) -> str:
"""AgentStore uses this attribute to display to the user what actions the current role should take."""
return self.next_todo_action
diff --git a/metagpt/roles/invoice_ocr_assistant.py b/metagpt/roles/invoice_ocr_assistant.py
index f5588974b..a39a48b97 100644
--- a/metagpt/roles/invoice_ocr_assistant.py
+++ b/metagpt/roles/invoice_ocr_assistant.py
@@ -60,7 +60,7 @@ class InvoiceOCRAssistant(Role):
def __init__(self, **kwargs):
super().__init__(**kwargs)
- self._init_actions([InvoiceOCR])
+ self.set_actions([InvoiceOCR])
self._set_react_mode(react_mode=RoleReactMode.BY_ORDER.value)
async def _act(self) -> Message:
@@ -82,12 +82,12 @@ class InvoiceOCRAssistant(Role):
resp = await todo.run(file_path)
if len(resp) == 1:
# Single file support for questioning based on OCR recognition results
- self._init_actions([GenerateTable, ReplyQuestion])
+ self.set_actions([GenerateTable, ReplyQuestion])
self.orc_data = resp[0]
else:
- self._init_actions([GenerateTable])
+ self.set_actions([GenerateTable])
- self.rc.todo = None
+ self.set_todo(None)
content = INVOICE_OCR_SUCCESS
resp = OCRResults(ocr_result=json.dumps(resp))
msg = Message(content=content, instruct_content=resp)
diff --git a/metagpt/roles/kaggle_manager.py b/metagpt/roles/kaggle_manager.py
deleted file mode 100644
index 3ef573a8c..000000000
--- a/metagpt/roles/kaggle_manager.py
+++ /dev/null
@@ -1,153 +0,0 @@
-import json
-import os
-import subprocess
-
-import fire
-import pandas as pd
-
-from metagpt.actions import Action, UserRequirement
-from metagpt.actions.ml_da_action import SummarizeAnalysis
-from metagpt.config import CONFIG
-from metagpt.logs import logger
-from metagpt.roles import Role
-from metagpt.schema import Message
-from metagpt.utils.common import CodeParser
-
-os.environ["KAGGLE_USERNAME"] = CONFIG.kaggle_username
-os.environ["KAGGLE_KEY"] = CONFIG.kaggle_key
-
-
-def run_command(cmd):
- print(cmd)
- output = subprocess.run(cmd, shell=True, capture_output=True, text=True)
- if output.returncode != 0:
- print("Error output:", output.stderr)
- exit()
- else:
- print(output.stdout)
- return output.stdout
-
-
-class DownloadData(Action):
- async def run(self, competition, data_desc="") -> str:
- data_path = CONFIG.workspace_path / competition
-
- output = run_command(f"kaggle competitions list --search {competition}")
- assert output != "No competitions found", "You must provide the correct competition name"
-
- run_command(f"kaggle competitions download {competition} --path {WORKSPACE_ROOT}")
-
- if not os.path.exists(data_path):
- # if True:
- # run_command(f"rm -r {data_path / '*'}")
- run_command(f"unzip -o {CONFIG.workspace_path / '*.zip'} -d {data_path}") # FIXME: not safe
-
- file_list = run_command(f"ls {data_path}")
-
- rsp = f"""
- Location:
- Data downloaded at {data_path} folder, including {file_list}
- Data Description:
- {data_desc}
- """
- return rsp
-
-
-class SubmitResult(Action):
- PROMPT_TEMPLATE: str = """
- # Summary
- __summary__
- # Your task
- Extract the file path for test set prediction from the summary above, output a json following the format:
- ```json
- {"file_path": str = "the file path, for example, /path/to/the/prediction/file/xxx.csv, /path/to/the/prediction/file/xxx.xlsx"}
- ```
- """
-
- def __init__(self, name: str = "", context=None, llm=None) -> str:
- super().__init__(name, context, llm)
-
- async def _parse_submit_file_path(self, context) -> str:
- prompt = self.PROMPT_TEMPLATE.replace("__summary__", context)
- rsp = await self._aask(prompt)
- rsp = CodeParser.parse_code(block=None, text=rsp)
- file_path = json.loads(rsp)["file_path"]
- return file_path
-
- async def run(self, competition, submit_message="") -> str:
- submit_file_path = await self._parse_submit_file_path(submit_message)
-
- data_path = CONFIG.workspace_path / competition
- submit_message = submit_message.replace("'", "")
-
- run_command(f"kaggle competitions submit {competition} -f {submit_file_path} -m '{submit_message}'")
- run_command(f"kaggle competitions leaderboard --show --csv {competition} > {data_path / 'leaderboard.csv'}")
- run_command(f"kaggle competitions submissions --csv {competition} > {data_path / 'submission.csv'}")
-
- leaderboard = pd.read_csv(data_path / "leaderboard.csv")
- submission = pd.read_csv(data_path / "submission.csv")
- print(submission) # submission.to_json(orient="records")
-
- submission_score = submission.loc[0, "publicScore"]
- best_score = max(submission["publicScore"]) # might be min
- rank = leaderboard.loc[leaderboard["score"] == best_score].index[0]
- rank_pct = round(rank / len(leaderboard), 4) * 100
-
- submission_summary = f"""
- # All histories:
- {submission.head(5).to_string()}
- # Current
- Current submission score: {submission_score}, best score: {best_score}, best rank: {rank} (top {rank_pct}%)
- """
- logger.info(submission_summary)
- return submission_summary
-
-
-class KaggleManager(Role):
- def __init__(self, name="ABC", profile="KaggleManager", goal="", competition="titanic", data_desc=""):
- super().__init__(name=name, profile=profile, goal=goal)
- self._init_actions([DownloadData, SubmitResult])
- self._watch([UserRequirement, SummarizeAnalysis])
- self.competition = competition
- self.data_desc = data_desc # currently passed in, later can be scrapped down from web by another Role
-
- async def _think(self):
- observed = self.get_memories()[-1].cause_by
- if observed == UserRequirement:
- self._set_state(0) # DownloadData, get competition of interest from human, download datasets
- elif observed == SummarizeAnalysis:
- self._set_state(1) # SubmitResult, get prediction from MLEngineer and submit it to Kaggle
-
- async def _act(self):
- todo = self.rc.todo
- logger.info(f"{self._setting}: ready to {self.rc.todo}")
-
- if isinstance(todo, DownloadData):
- rsp = await todo.run(self.competition, self.data_desc)
-
- elif isinstance(todo, SubmitResult):
- submit_message = self.get_memories()[
- -1
- ].content # use analysis summary from MLEngineer as submission message
- rsp = await todo.run(competition=self.competition, submit_message=submit_message)
-
- msg = Message(content=rsp, role="user", cause_by=type(todo))
-
- return msg
-
-
-if __name__ == "__main__":
- competition, data_desc, requirement = (
- "titanic",
- "Training set is train.csv.\nTest set is test.csv. We also include gender_submission.csv, a set of predictions that assume all and only female passengers survive, as an example of what a submission file should look like.",
- "Run EDA on the train dataset, train a model to predict survival (20% as validation) and save it, predict the test set using saved model, save the test result according to format",
- )
-
- summary = "I used Python with pandas for data preprocessing, sklearn's RandomForestClassifier for modeling, and achieved 82.12% accuracy on validation. Predictions saved at '/Users/gary/Desktop/data_agents_opt/workspace/titanic/gender_submission.csv'."
-
- async def main(requirement: str = requirement):
- role = KaggleManager(competition=competition, data_desc=data_desc)
- # await role.run(Message(content="", cause_by=UserRequirement))
- await role.run(Message(content=summary, cause_by=SummarizeAnalysis))
-
- fire.Fire(main)
diff --git a/metagpt/roles/ml_engineer.py b/metagpt/roles/ml_engineer.py
index d1a22b9d3..e7abee560 100644
--- a/metagpt/roles/ml_engineer.py
+++ b/metagpt/roles/ml_engineer.py
@@ -1,7 +1,6 @@
from metagpt.actions.debug_code import DebugCode
from metagpt.actions.execute_code import ExecutePyCode
-from metagpt.actions.ml_da_action import UpdateDataColumns
-from metagpt.actions.write_analysis_code import WriteCodeWithToolsML
+from metagpt.actions.ml_action import UpdateDataColumns, WriteCodeWithToolsML
from metagpt.logs import logger
from metagpt.roles.code_interpreter import CodeInterpreter
from metagpt.tools.tool_data_type import ToolTypeEnum
diff --git a/metagpt/roles/product_manager.py b/metagpt/roles/product_manager.py
index 1d82ac3f2..fbe139a99 100644
--- a/metagpt/roles/product_manager.py
+++ b/metagpt/roles/product_manager.py
@@ -9,7 +9,6 @@
from metagpt.actions import UserRequirement, WritePRD
from metagpt.actions.prepare_documents import PrepareDocuments
-from metagpt.config import CONFIG
from metagpt.roles.role import Role
from metagpt.utils.common import any_to_name
@@ -34,24 +33,19 @@ class ProductManager(Role):
def __init__(self, **kwargs) -> None:
super().__init__(**kwargs)
- self._init_actions([PrepareDocuments, WritePRD])
+ self.set_actions([PrepareDocuments, WritePRD])
self._watch([UserRequirement, PrepareDocuments])
self.todo_action = any_to_name(PrepareDocuments)
async def _think(self) -> bool:
"""Decide what to do"""
- if CONFIG.git_repo and not CONFIG.git_reinit:
+ if self.git_repo and not self.config.git_reinit:
self._set_state(1)
else:
self._set_state(0)
- CONFIG.git_reinit = False
+ self.config.git_reinit = False
self.todo_action = any_to_name(WritePRD)
return bool(self.rc.todo)
async def _observe(self, ignore_memory=False) -> int:
return await super()._observe(ignore_memory=True)
-
- @property
- def todo(self) -> str:
- """AgentStore uses this attribute to display to the user what actions the current role should take."""
- return self.todo_action
diff --git a/metagpt/roles/project_manager.py b/metagpt/roles/project_manager.py
index 1fad4afc2..422d2889b 100644
--- a/metagpt/roles/project_manager.py
+++ b/metagpt/roles/project_manager.py
@@ -33,5 +33,5 @@ class ProjectManager(Role):
def __init__(self, **kwargs) -> None:
super().__init__(**kwargs)
- self._init_actions([WriteTasks])
+ self.set_actions([WriteTasks])
self._watch([WriteDesign])
diff --git a/metagpt/roles/qa_engineer.py b/metagpt/roles/qa_engineer.py
index b1d06d122..c73c10ef3 100644
--- a/metagpt/roles/qa_engineer.py
+++ b/metagpt/roles/qa_engineer.py
@@ -15,20 +15,13 @@
of SummarizeCode.
"""
-
from metagpt.actions import DebugError, RunCode, WriteTest
from metagpt.actions.summarize_code import SummarizeCode
-from metagpt.config import CONFIG
-from metagpt.const import (
- MESSAGE_ROUTE_TO_NONE,
- TEST_CODES_FILE_REPO,
- TEST_OUTPUTS_FILE_REPO,
-)
+from metagpt.const import MESSAGE_ROUTE_TO_NONE
from metagpt.logs import logger
from metagpt.roles import Role
from metagpt.schema import Document, Message, RunCodeContext, TestingContext
from metagpt.utils.common import any_to_str_set, parse_recipient
-from metagpt.utils.file_repository import FileRepository
class QaEngineer(Role):
@@ -36,7 +29,8 @@ class QaEngineer(Role):
profile: str = "QaEngineer"
goal: str = "Write comprehensive and robust tests to ensure codes will work as expected without bugs"
constraints: str = (
- "The test code you write should conform to code standard like PEP8, be modular, " "easy to read and maintain"
+ "The test code you write should conform to code standard like PEP8, be modular, easy to read and maintain."
+ "Use same language as user requirement"
)
test_round_allowed: int = 5
test_round: int = 0
@@ -46,34 +40,35 @@ class QaEngineer(Role):
# FIXME: a bit hack here, only init one action to circumvent _think() logic,
# will overwrite _think() in future updates
- self._init_actions([WriteTest])
+ self.set_actions([WriteTest])
self._watch([SummarizeCode, WriteTest, RunCode, DebugError])
self.test_round = 0
async def _write_test(self, message: Message) -> None:
- src_file_repo = CONFIG.git_repo.new_file_repository(CONFIG.src_workspace)
+ src_file_repo = self.project_repo.with_src_path(self.context.src_workspace).srcs
changed_files = set(src_file_repo.changed_files.keys())
# Unit tests only.
- if CONFIG.reqa_file and CONFIG.reqa_file not in changed_files:
- changed_files.add(CONFIG.reqa_file)
- tests_file_repo = CONFIG.git_repo.new_file_repository(TEST_CODES_FILE_REPO)
+ if self.config.reqa_file and self.config.reqa_file not in changed_files:
+ changed_files.add(self.config.reqa_file)
for filename in changed_files:
# write tests
if not filename or "test" in filename:
continue
code_doc = await src_file_repo.get(filename)
- test_doc = await tests_file_repo.get("test_" + code_doc.filename)
+ if not code_doc:
+ continue
+ if not code_doc.filename.endswith(".py"):
+ continue
+ test_doc = await self.project_repo.tests.get("test_" + code_doc.filename)
if not test_doc:
test_doc = Document(
- root_path=str(tests_file_repo.root_path), filename="test_" + code_doc.filename, content=""
+ root_path=str(self.project_repo.tests.root_path), filename="test_" + code_doc.filename, content=""
)
logger.info(f"Writing {test_doc.filename}..")
context = TestingContext(filename=test_doc.filename, test_doc=test_doc, code_doc=code_doc)
- context = await WriteTest(context=context, llm=self.llm).run()
- await tests_file_repo.save(
- filename=context.test_doc.filename,
- content=context.test_doc.content,
- dependencies={context.code_doc.root_relative_path},
+ context = await WriteTest(i_context=context, context=self.context, llm=self.llm).run()
+ await self.project_repo.tests.save_doc(
+ doc=context.test_doc, dependencies={context.code_doc.root_relative_path}
)
# prepare context for run tests in next round
@@ -81,8 +76,8 @@ class QaEngineer(Role):
command=["python", context.test_doc.root_relative_path],
code_filename=context.code_doc.filename,
test_filename=context.test_doc.filename,
- working_directory=str(CONFIG.git_repo.workdir),
- additional_python_paths=[str(CONFIG.src_workspace)],
+ working_directory=str(self.project_repo.workdir),
+ additional_python_paths=[str(self.context.src_workspace)],
)
self.publish_message(
Message(
@@ -94,21 +89,23 @@ class QaEngineer(Role):
)
)
- logger.info(f"Done {str(tests_file_repo.workdir)} generating.")
+ logger.info(f"Done {str(self.project_repo.tests.workdir)} generating.")
async def _run_code(self, msg):
run_code_context = RunCodeContext.loads(msg.content)
- src_doc = await CONFIG.git_repo.new_file_repository(CONFIG.src_workspace).get(run_code_context.code_filename)
+ src_doc = await self.project_repo.with_src_path(self.context.src_workspace).srcs.get(
+ run_code_context.code_filename
+ )
if not src_doc:
return
- test_doc = await CONFIG.git_repo.new_file_repository(TEST_CODES_FILE_REPO).get(run_code_context.test_filename)
+ test_doc = await self.project_repo.tests.get(run_code_context.test_filename)
if not test_doc:
return
run_code_context.code = src_doc.content
run_code_context.test_code = test_doc.content
- result = await RunCode(context=run_code_context, llm=self.llm).run()
+ result = await RunCode(i_context=run_code_context, context=self.context, llm=self.llm).run()
run_code_context.output_filename = run_code_context.test_filename + ".json"
- await CONFIG.git_repo.new_file_repository(TEST_OUTPUTS_FILE_REPO).save(
+ await self.project_repo.test_outputs.save(
filename=run_code_context.output_filename,
content=result.model_dump_json(),
dependencies={src_doc.root_relative_path, test_doc.root_relative_path},
@@ -130,10 +127,8 @@ class QaEngineer(Role):
async def _debug_error(self, msg):
run_code_context = RunCodeContext.loads(msg.content)
- code = await DebugError(context=run_code_context, llm=self.llm).run()
- await FileRepository.save_file(
- filename=run_code_context.test_filename, content=code, relative_path=TEST_CODES_FILE_REPO
- )
+ code = await DebugError(i_context=run_code_context, context=self.context, llm=self.llm).run()
+ await self.project_repo.tests.save(filename=run_code_context.test_filename, content=code)
run_code_context.output = None
self.publish_message(
Message(
diff --git a/metagpt/roles/researcher.py b/metagpt/roles/researcher.py
index 15f6c9a22..137cfdb4c 100644
--- a/metagpt/roles/researcher.py
+++ b/metagpt/roles/researcher.py
@@ -34,7 +34,7 @@ class Researcher(Role):
def __init__(self, **kwargs):
super().__init__(**kwargs)
- self._init_actions(
+ self.set_actions(
[CollectLinks(name=self.name), WebBrowseAndSummarize(name=self.name), ConductResearch(name=self.name)]
)
self._set_react_mode(react_mode=RoleReactMode.BY_ORDER.value)
@@ -49,7 +49,7 @@ class Researcher(Role):
if self.rc.state + 1 < len(self.states):
self._set_state(self.rc.state + 1)
else:
- self.rc.todo = None
+ self.set_todo(None)
return False
async def _act(self) -> Message:
diff --git a/metagpt/roles/role.py b/metagpt/roles/role.py
index 21e48a127..9efcf470e 100644
--- a/metagpt/roles/role.py
+++ b/metagpt/roles/role.py
@@ -23,29 +23,21 @@
from __future__ import annotations
from enum import Enum
-from pathlib import Path
-from typing import Any, Iterable, Optional, Set, Type
+from typing import Any, Iterable, Optional, Set, Type, Union
from pydantic import BaseModel, ConfigDict, Field, SerializeAsAny, model_validator
from metagpt.actions import Action, ActionOutput
from metagpt.actions.action_node import ActionNode
from metagpt.actions.add_requirement import UserRequirement
-from metagpt.const import SERDESER_PATH
-from metagpt.llm import LLM, HumanProvider
+from metagpt.context_mixin import ContextMixin
from metagpt.logs import logger
from metagpt.memory import Memory
from metagpt.plan.planner import Planner
-from metagpt.provider.base_llm import BaseLLM
-from metagpt.schema import Message, MessageQueue, SerializationMixin, Task, TaskResult
-from metagpt.utils.common import (
- any_to_name,
- any_to_str,
- import_class,
- read_json_file,
- role_raise_decorator,
- write_json_file,
-)
+from metagpt.provider import HumanProvider
+from metagpt.schema import Message, MessageQueue, SerializationMixin
+from metagpt.utils.common import any_to_name, any_to_str, role_raise_decorator
+from metagpt.utils.project_repo import ProjectRepo
from metagpt.utils.repair_llm_raw_output import extract_state_value_from_output
PREFIX_TEMPLATE = """You are a {profile}, named {name}, your goal is {goal}. """
@@ -113,7 +105,7 @@ class RoleContext(BaseModel):
max_react_loop: int = 1
def check(self, role_id: str):
- # if hasattr(CONFIG, "long_term_memory") and CONFIG.long_term_memory:
+ # if hasattr(CONFIG, "enable_longterm_memory") and CONFIG.enable_longterm_memory:
# self.long_term_memory.recover_memory(role_id, self)
# self.memory = self.long_term_memory # use memory to act as long_term_memory for unify operation
pass
@@ -128,10 +120,10 @@ class RoleContext(BaseModel):
return self.memory.get()
-class Role(SerializationMixin, is_polymorphic_base=True):
+class Role(SerializationMixin, ContextMixin, BaseModel):
"""Role/Agent"""
- model_config = ConfigDict(arbitrary_types_allowed=True, exclude=["llm"])
+ model_config = ConfigDict(arbitrary_types_allowed=True, extra="ignore")
name: str = ""
profile: str = ""
@@ -140,13 +132,19 @@ class Role(SerializationMixin, is_polymorphic_base=True):
desc: str = ""
is_human: bool = False
- llm: BaseLLM = Field(default_factory=LLM, exclude=True) # Each role has its own LLM, use different system message
role_id: str = ""
states: list[str] = []
+
+ # scenarios to set action system_prompt:
+ # 1. `__init__` while using Role(actions=[...])
+ # 2. add action to role while using `role.set_action(action)`
+ # 3. set_todo while using `role.set_todo(action)`
+ # 4. when role.system_prompt is being updated (e.g. by `role.system_prompt = "..."`)
+ # Additional, if llm is not set, we will use role's llm
actions: list[SerializeAsAny[Action]] = Field(default=[], validate_default=True)
rc: RoleContext = Field(default_factory=RoleContext)
- subscription: set[str] = set()
- planner: Planner = None
+ addresses: set[str] = set()
+ planner: Planner = Field(default_factory=Planner)
# builtin variables
recovered: bool = False # to tag if a recovered role
@@ -154,27 +152,85 @@ class Role(SerializationMixin, is_polymorphic_base=True):
__hash__ = object.__hash__ # support Role as hashable type in `Environment.members`
- @model_validator(mode="after")
- def check_subscription(self):
- if not self.subscription:
- self.subscription = {any_to_str(self), self.name} if self.name else {any_to_str(self)}
- return self
-
def __init__(self, **data: Any):
- # --- avoid PydanticUndefinedAnnotation name 'Environment' is not defined #
- from metagpt.environment import Environment
-
- Environment
- # ------
- Role.model_rebuild()
+ self.pydantic_rebuild_model()
super().__init__(**data)
if self.is_human:
- self.llm = HumanProvider()
+ self.llm = HumanProvider(None)
+ self._check_actions()
self.llm.system_prompt = self._get_prefix()
self._watch(data.get("watch") or [UserRequirement])
+ if self.latest_observed_msg:
+ self.recovered = True
+
+ @staticmethod
+ def pydantic_rebuild_model():
+ """Rebuild model to avoid `RecursionError: maximum recursion depth exceeded in comparison`"""
+ from metagpt.environment import Environment
+
+ Environment
+ Role.model_rebuild()
+
+ @property
+ def todo(self) -> Action:
+ """Get action to do"""
+ return self.rc.todo
+
+ def set_todo(self, value: Optional[Action]):
+ """Set action to do and update context"""
+ if value:
+ value.context = self.context
+ self.rc.todo = value
+
+ @property
+ def git_repo(self):
+ """Git repo"""
+ return self.context.git_repo
+
+ @git_repo.setter
+ def git_repo(self, value):
+ self.context.git_repo = value
+
+ @property
+ def src_workspace(self):
+ """Source workspace under git repo"""
+ return self.context.src_workspace
+
+ @src_workspace.setter
+ def src_workspace(self, value):
+ self.context.src_workspace = value
+
+ @property
+ def project_repo(self) -> ProjectRepo:
+ project_repo = ProjectRepo(self.context.git_repo)
+ return project_repo.with_src_path(self.context.src_workspace) if self.context.src_workspace else project_repo
+
+ @property
+ def prompt_schema(self):
+ """Prompt schema: json/markdown"""
+ return self.config.prompt_schema
+
+ @property
+ def project_name(self):
+ return self.config.project_name
+
+ @project_name.setter
+ def project_name(self, value):
+ self.config.project_name = value
+
+ @property
+ def project_path(self):
+ return self.config.project_path
+
+ @model_validator(mode="after")
+ def check_addresses(self):
+ if not self.addresses:
+ self.addresses = {any_to_str(self), self.name} if self.name else {any_to_str(self)}
+ return self
+
def _reset(self):
self.states = []
self.actions = []
@@ -183,59 +239,32 @@ class Role(SerializationMixin, is_polymorphic_base=True):
def _setting(self):
return f"{self.name}({self.profile})"
- def serialize(self, stg_path: Path = None):
- stg_path = (
- SERDESER_PATH.joinpath(f"team/environment/roles/{self.__class__.__name__}_{self.name}")
- if stg_path is None
- else stg_path
- )
+ def _check_actions(self):
+ """Check actions and set llm and prefix for each action."""
+ self.set_actions(self.actions)
+ return self
- role_info = self.model_dump(exclude={"rc": {"memory": True, "msg_buffer": True}, "llm": True})
- role_info.update({"role_class": self.__class__.__name__, "module_name": self.__module__})
- role_info_path = stg_path.joinpath("role_info.json")
- write_json_file(role_info_path, role_info)
-
- self.rc.memory.serialize(stg_path) # serialize role's memory alone
-
- @classmethod
- def deserialize(cls, stg_path: Path) -> "Role":
- """stg_path = ./storage/team/environment/roles/{role_class}_{role_name}"""
- role_info_path = stg_path.joinpath("role_info.json")
- role_info = read_json_file(role_info_path)
-
- role_class_str = role_info.pop("role_class")
- module_name = role_info.pop("module_name")
- role_class = import_class(class_name=role_class_str, module_name=module_name)
-
- role = role_class(**role_info) # initiate particular Role
- role.set_recovered(True) # set True to make a tag
-
- role_memory = Memory.deserialize(stg_path)
- role.set_memory(role_memory)
-
- return role
-
- def _init_action_system_message(self, action: Action):
+ def _init_action(self, action: Action):
+ if not action.private_config:
+ action.set_llm(self.llm, override=True)
+ else:
+ action.set_llm(self.llm, override=False)
action.set_prefix(self._get_prefix())
- def refresh_system_message(self):
- self.llm.system_prompt = self._get_prefix()
+ def set_action(self, action: Action):
+ """Add action to the role."""
+ self.set_actions([action])
- def set_recovered(self, recovered: bool = False):
- self.recovered = recovered
+ def set_actions(self, actions: list[Union[Action, Type[Action]]]):
+ """Add actions to the role.
- def set_memory(self, memory: Memory):
- self.rc.memory = memory
-
- def init_actions(self, actions):
- self._init_actions(actions)
-
- def _init_actions(self, actions):
+ Args:
+ actions: list of Action classes or instances
+ """
self._reset()
- for idx, action in enumerate(actions):
+ for action in actions:
if not isinstance(action, Action):
- ## 默认初始化
- i = action(name="", llm=self.llm)
+ i = action(context=self.context)
else:
if self.is_human and not isinstance(action.llm, HumanProvider):
logger.warning(
@@ -244,9 +273,9 @@ class Role(SerializationMixin, is_polymorphic_base=True):
f"try passing in Action classes instead of initialized instances"
)
i = action
- self._init_action_system_message(i)
+ self._init_action(i)
self.actions.append(i)
- self.states.append(f"{idx}. {action}")
+ self.states.append(f"{len(self.actions)}. {action}")
def _set_react_mode(self, react_mode: str, max_react_loop: int = 1, auto_run: bool = True, use_tools: bool = False):
"""Set strategy of the Role reacting to observed Message. Variation lies in how
@@ -284,33 +313,29 @@ class Role(SerializationMixin, is_polymorphic_base=True):
def is_watch(self, caused_by: str):
return caused_by in self.rc.watch
- def subscribe(self, tags: Set[str]):
+ def set_addresses(self, addresses: Set[str]):
"""Used to receive Messages with certain tags from the environment. Message will be put into personal message
buffer to be further processed in _observe. By default, a Role subscribes Messages with a tag of its own name
or profile.
"""
- self.subscription = tags
+ self.addresses = addresses
if self.rc.env: # According to the routing feature plan in Chapter 2.2.3.2 of RFC 113
- self.rc.env.set_subscription(self, self.subscription)
+ self.rc.env.set_addresses(self, self.addresses)
def _set_state(self, state: int):
"""Update the current state."""
self.rc.state = state
logger.debug(f"actions={self.actions}, state={state}")
- self.rc.todo = self.actions[self.rc.state] if state >= 0 else None
+ self.set_todo(self.actions[self.rc.state] if state >= 0 else None)
def set_env(self, env: "Environment"):
"""Set the environment in which the role works. The role can talk to the environment and can also receive
messages by observing."""
self.rc.env = env
if env:
- env.set_subscription(self, self.subscription)
- self.refresh_system_message() # add env message to system message
-
- @property
- def action_count(self):
- """Return number of action"""
- return len(self.actions)
+ env.set_addresses(self, self.addresses)
+ self.llm.system_prompt = self._get_prefix()
+ self.set_actions(self.actions) # reset actions to update llm and prefix
def _get_prefix(self):
"""Get the role prefix"""
@@ -323,7 +348,8 @@ class Role(SerializationMixin, is_polymorphic_base=True):
prefix += CONSTRAINT_TEMPLATE.format(**{"constraints": self.constraints})
if self.rc.env and self.rc.env.desc:
- other_role_names = ", ".join(self.rc.env.role_names())
+ all_roles = self.rc.env.role_names()
+ other_role_names = ", ".join([r for r in all_roles if r != self.name])
env_desc = f"You are in {self.rc.env.desc} with roles({other_role_names})."
prefix += env_desc
return prefix
@@ -338,7 +364,7 @@ class Role(SerializationMixin, is_polymorphic_base=True):
if self.recovered and self.rc.state >= 0:
self._set_state(self.rc.state) # action to run from recovered state
- self.set_recovered(False) # avoid max_react_loop out of work
+ self.recovered = False # avoid max_react_loop out of work
return True
prompt = self._get_prefix()
@@ -436,7 +462,7 @@ class Role(SerializationMixin, is_polymorphic_base=True):
break
# act
logger.debug(f"{self._setting}: {self.rc.state=}, will do {self.rc.todo}")
- rsp = await self._act() # 这个rsp是否需要publish_message?
+ rsp = await self._act()
actions_taken += 1
return rsp # return output from the last action
@@ -452,10 +478,11 @@ class Role(SerializationMixin, is_polymorphic_base=True):
async def _plan_and_act(self) -> Message:
"""first plan, then execute an action sequence, i.e. _think (of a plan) -> _act -> _act -> ... Use llm to come up with the plan dynamically."""
- ### Common Procedure in both single- and multi-agent setting ###
- # create initial plan and update until confirmation
- await self.planner.update_plan()
+ # create initial plan and update it until confirmation
+ goal = self.rc.memory.get()[-1].content # retreive latest user requirement
+ await self.planner.update_plan(goal=goal)
+ # take on tasks until all finished
while self.planner.current_task:
task = self.planner.current_task
logger.info(f"ready to take on task {task}")
@@ -463,25 +490,10 @@ class Role(SerializationMixin, is_polymorphic_base=True):
# take on current task
task_result = await self._act_on_task(task)
- # ask for acceptance, users can other refuse and change tasks in the plan
- review, task_result_confirmed = await self.planner.ask_review(task_result)
+ # process the result, such as reviewing, confirming, plan updating
+ await self.planner.process_task_result(task_result)
- if task_result_confirmed:
- # tick off this task and record progress
- await self.planner.confirm_task(task, task_result, review)
-
- elif "redo" in review:
- # Ask the Role to redo this task with help of review feedback,
- # useful when the code run is successful but the procedure or result is not what we want
- continue
-
- else:
- # update plan according to user's feedback and to take on changed tasks
- await self.planner.update_plan()
-
- completed_plan_memory = self.planner.get_useful_memories() # completed plan as a outcome
-
- rsp = completed_plan_memory[0]
+ rsp = self.planner.get_useful_memories()[0] # return the completed plan as a response
self.rc.memory.add(rsp) # add to persistent memory
@@ -509,6 +521,8 @@ class Role(SerializationMixin, is_polymorphic_base=True):
rsp = await self._act_by_order()
elif self.rc.react_mode == RoleReactMode.PLAN_AND_ACT:
rsp = await self._plan_and_act()
+ else:
+ raise ValueError(f"Unsupported react mode: {self.rc.react_mode}")
self._set_state(state=-1) # current reaction is complete, reset state to -1 and todo back to None
return rsp
@@ -530,7 +544,6 @@ class Role(SerializationMixin, is_polymorphic_base=True):
if not msg.cause_by:
msg.cause_by = UserRequirement
self.put_message(msg)
-
if not await self._observe():
# If there is no new information, suspend and wait
logger.debug(f"{self._setting}: no news. waiting.")
@@ -539,7 +552,7 @@ class Role(SerializationMixin, is_polymorphic_base=True):
rsp = await self.react()
# Reset the next action to be taken.
- self.rc.todo = None
+ self.set_todo(None)
# Send the response message to the Environment object to have it relay the message to the subscribers.
self.publish_message(rsp)
return rsp
@@ -550,18 +563,34 @@ class Role(SerializationMixin, is_polymorphic_base=True):
return not self.rc.news and not self.rc.todo and self.rc.msg_buffer.empty()
async def think(self) -> Action:
- """The exported `think` function"""
+ """
+ Export SDK API, used by AgentStore RPC.
+ The exported `think` function
+ """
+ await self._observe() # For compatibility with the old version of the Agent.
await self._think()
return self.rc.todo
async def act(self) -> ActionOutput:
- """The exported `act` function"""
+ """
+ Export SDK API, used by AgentStore RPC.
+ The exported `act` function
+ """
msg = await self._act()
return ActionOutput(content=msg.content, instruct_content=msg.instruct_content)
@property
- def todo(self) -> str:
- """AgentStore uses this attribute to display to the user what actions the current role should take."""
+ def action_description(self) -> str:
+ """
+ Export SDK API, used by AgentStore RPC and Agent.
+ AgentStore uses this attribute to display to the user what actions the current role should take.
+ `Role` provides the default property, and this property should be overridden by children classes if necessary,
+ as demonstrated by the `Engineer` class.
+ """
+ if self.rc.todo:
+ if self.rc.todo.desc:
+ return self.rc.todo.desc
+ return any_to_name(self.rc.todo)
if self.actions:
return any_to_name(self.actions[0])
return ""
diff --git a/metagpt/roles/sales.py b/metagpt/roles/sales.py
index ca1cfee85..7929ce7fe 100644
--- a/metagpt/roles/sales.py
+++ b/metagpt/roles/sales.py
@@ -38,5 +38,5 @@ class Sales(Role):
action = SearchAndSummarize(name="", engine=SearchEngineType.CUSTOM_ENGINE, search_func=store.asearch)
else:
action = SearchAndSummarize()
- self._init_actions([action])
+ self.set_actions([action])
self._watch([UserRequirement])
diff --git a/metagpt/roles/searcher.py b/metagpt/roles/searcher.py
index e713f7697..19a73a40e 100644
--- a/metagpt/roles/searcher.py
+++ b/metagpt/roles/searcher.py
@@ -10,8 +10,9 @@
from pydantic import Field
-from metagpt.actions import ActionOutput, SearchAndSummarize
+from metagpt.actions import SearchAndSummarize
from metagpt.actions.action_node import ActionNode
+from metagpt.actions.action_output import ActionOutput
from metagpt.logs import logger
from metagpt.roles import Role
from metagpt.schema import Message
@@ -48,12 +49,12 @@ class Searcher(Role):
engine (SearchEngineType): The type of search engine to use.
"""
super().__init__(**kwargs)
- self._init_actions([SearchAndSummarize(engine=self.engine)])
+ self.set_actions([SearchAndSummarize(engine=self.engine)])
def set_search_func(self, search_func):
"""Sets a custom search function for the searcher."""
action = SearchAndSummarize(name="", engine=SearchEngineType.CUSTOM_ENGINE, search_func=search_func)
- self._init_actions([action])
+ self.set_actions([action])
async def _act_sp(self) -> Message:
"""Performs the search action in a single process."""
diff --git a/metagpt/roles/sk_agent.py b/metagpt/roles/sk_agent.py
index 8921774f0..71df55fcc 100644
--- a/metagpt/roles/sk_agent.py
+++ b/metagpt/roles/sk_agent.py
@@ -17,9 +17,7 @@ from semantic_kernel.planning.basic_planner import BasicPlanner, Plan
from metagpt.actions import UserRequirement
from metagpt.actions.execute_task import ExecuteTask
-from metagpt.llm import LLM
from metagpt.logs import logger
-from metagpt.provider.base_llm import BaseLLM
from metagpt.roles import Role
from metagpt.schema import Message
from metagpt.utils.make_sk_kernel import make_sk_kernel
@@ -44,7 +42,6 @@ class SkAgent(Role):
plan: Plan = Field(default=None, exclude=True)
planner_cls: Any = None
planner: Union[BasicPlanner, SequentialPlanner, ActionPlanner] = None
- llm: BaseLLM = Field(default_factory=LLM)
kernel: Kernel = Field(default_factory=Kernel)
import_semantic_skill_from_directory: Callable = Field(default=None, exclude=True)
import_skill: Callable = Field(default=None, exclude=True)
@@ -52,7 +49,7 @@ class SkAgent(Role):
def __init__(self, **data: Any) -> None:
"""Initializes the Engineer role with given attributes."""
super().__init__(**data)
- self._init_actions([ExecuteTask()])
+ self.set_actions([ExecuteTask()])
self._watch([UserRequirement])
self.kernel = make_sk_kernel()
diff --git a/metagpt/roles/teacher.py b/metagpt/roles/teacher.py
index 5449fe828..d6715dcd1 100644
--- a/metagpt/roles/teacher.py
+++ b/metagpt/roles/teacher.py
@@ -11,15 +11,12 @@
import re
-import aiofiles
-
from metagpt.actions import UserRequirement
from metagpt.actions.write_teaching_plan import TeachingPlanBlock, WriteTeachingPlanPart
-from metagpt.config import CONFIG
from metagpt.logs import logger
from metagpt.roles import Role
from metagpt.schema import Message
-from metagpt.utils.common import any_to_str
+from metagpt.utils.common import any_to_str, awrite
class Teacher(Role):
@@ -34,11 +31,11 @@ class Teacher(Role):
def __init__(self, **kwargs):
super().__init__(**kwargs)
- self.name = WriteTeachingPlanPart.format_value(self.name)
- self.profile = WriteTeachingPlanPart.format_value(self.profile)
- self.goal = WriteTeachingPlanPart.format_value(self.goal)
- self.constraints = WriteTeachingPlanPart.format_value(self.constraints)
- self.desc = WriteTeachingPlanPart.format_value(self.desc)
+ self.name = WriteTeachingPlanPart.format_value(self.name, self.context)
+ self.profile = WriteTeachingPlanPart.format_value(self.profile, self.context)
+ self.goal = WriteTeachingPlanPart.format_value(self.goal, self.context)
+ self.constraints = WriteTeachingPlanPart.format_value(self.constraints, self.context)
+ self.desc = WriteTeachingPlanPart.format_value(self.desc, self.context)
async def _think(self) -> bool:
"""Everything will be done part by part."""
@@ -48,9 +45,9 @@ class Teacher(Role):
actions = []
print(TeachingPlanBlock.TOPICS)
for topic in TeachingPlanBlock.TOPICS:
- act = WriteTeachingPlanPart(context=self.rc.news[0].content, topic=topic, llm=self.llm)
+ act = WriteTeachingPlanPart(i_context=self.rc.news[0].content, topic=topic, llm=self.llm)
actions.append(act)
- self._init_actions(actions)
+ self.set_actions(actions)
if self.rc.todo is None:
self._set_state(0)
@@ -60,7 +57,7 @@ class Teacher(Role):
self._set_state(self.rc.state + 1)
return True
- self.rc.todo = None
+ self.set_todo(None)
return False
async def _react(self) -> Message:
@@ -81,14 +78,10 @@ class Teacher(Role):
async def save(self, content):
"""Save teaching plan"""
filename = Teacher.new_file_name(self.course_title)
- pathname = CONFIG.workspace_path / "teaching_plan"
+ pathname = self.config.workspace.path / "teaching_plan"
pathname.mkdir(exist_ok=True)
pathname = pathname / filename
- try:
- async with aiofiles.open(str(pathname), mode="w", encoding="utf-8") as writer:
- await writer.write(content)
- except Exception as e:
- logger.error(f"Save failed:{e}")
+ await awrite(pathname, content)
logger.info(f"Save to:{pathname}")
@staticmethod
diff --git a/metagpt/roles/tool_maker.py b/metagpt/roles/tool_maker.py
deleted file mode 100644
index 68d84b1e6..000000000
--- a/metagpt/roles/tool_maker.py
+++ /dev/null
@@ -1,53 +0,0 @@
-from pydantic import Field
-
-from metagpt.actions.ask_review import AskReview
-from metagpt.actions.execute_code import ExecutePyCode
-from metagpt.actions.write_analysis_code import MakeTools
-from metagpt.logs import logger
-from metagpt.roles import Role
-from metagpt.utils.common import remove_comments
-
-
-class ToolMaker(Role):
- execute_code: ExecutePyCode = Field(default_factory=ExecutePyCode, exclude=True)
-
- async def make_tool(self, code: str, instruction: str, task_id: str = "", auto_run=True):
- if len(remove_comments(code).split("\n")) < 5: # no need to consider trivial codes with fewer than 5 lines
- return
-
- logger.warning(
- f"Making tools for task_id {task_id}: \
- `{instruction}` \n code: \n {code}"
- )
- make_tools = MakeTools()
- make_tool_retries, make_tool_current_retry = 3, 0
- while True:
- # start make tools
- tool_code = await make_tools.run(code, instruction)
- make_tool_current_retry += 1
-
- # check tool_code by execute_code
- logger.info(f"Checking task_id {task_id} tool code by executor...")
- execute_result, execute_success = await self.execute_code.run(tool_code)
- if not execute_success:
- logger.error(f"Tool code faild to execute, \n{execute_result}\n.We will try to fix it ...")
- # end make tools
- if execute_success or make_tool_current_retry >= make_tool_retries:
- if make_tool_current_retry >= make_tool_retries:
- logger.error(
- f"We have tried the maximum number of attempts {make_tool_retries}\
- and still have not created tools for task_id {task_id} successfully,\
- we will skip it."
- )
- break
- # save successful tool code in udf
- if execute_success:
- _, confirmed = await self.ask_review(auto_run=auto_run)
- if confirmed:
- make_tools.save(tool_code)
-
- async def ask_review(self, auto_run: bool = True):
- if not auto_run:
- review, confirmed = await AskReview().run()
- return review, confirmed
- return "", True
diff --git a/metagpt/roles/tutorial_assistant.py b/metagpt/roles/tutorial_assistant.py
index 10bd82c60..6cf3a6469 100644
--- a/metagpt/roles/tutorial_assistant.py
+++ b/metagpt/roles/tutorial_assistant.py
@@ -40,7 +40,7 @@ class TutorialAssistant(Role):
def __init__(self, **kwargs):
super().__init__(**kwargs)
- self._init_actions([WriteDirectory(language=self.language)])
+ self.set_actions([WriteDirectory(language=self.language)])
self._set_react_mode(react_mode=RoleReactMode.BY_ORDER.value)
async def _handle_directory(self, titles: Dict) -> Message:
@@ -63,7 +63,7 @@ class TutorialAssistant(Role):
directory += f"- {key}\n"
for second_dir in first_dir[key]:
directory += f" - {second_dir}\n"
- self._init_actions(actions)
+ self.set_actions(actions)
async def _act(self) -> Message:
"""Perform an action as determined by the role.
diff --git a/metagpt/schema.py b/metagpt/schema.py
index e69f432db..08f97be94 100644
--- a/metagpt/schema.py
+++ b/metagpt/schema.py
@@ -23,7 +23,7 @@ from abc import ABC
from asyncio import Queue, QueueEmpty, wait_for
from json import JSONDecodeError
from pathlib import Path
-from typing import Any, Callable, Dict, List, Optional, Type, TypeVar, Union
+from typing import Any, Dict, Iterable, List, Optional, Type, TypeVar, Union
from pydantic import (
BaseModel,
@@ -32,15 +32,17 @@ from pydantic import (
PrivateAttr,
field_serializer,
field_validator,
+ model_serializer,
+ model_validator,
)
-from pydantic_core import core_schema
-from metagpt.config import CONFIG
from metagpt.const import (
+ CODE_PLAN_AND_CHANGE_FILENAME,
MESSAGE_ROUTE_CAUSE_BY,
MESSAGE_ROUTE_FROM,
MESSAGE_ROUTE_TO,
MESSAGE_ROUTE_TO_ALL,
+ PRDS_FILE_REPO,
SYSTEM_DESIGN_FILE_REPO,
TASK_FILE_REPO,
)
@@ -54,7 +56,7 @@ from metagpt.utils.serialize import (
)
-class SerializationMixin(BaseModel):
+class SerializationMixin(BaseModel, extra="forbid"):
"""
PolyMorphic subclasses Serialization / Deserialization Mixin
- First of all, we need to know that pydantic is not designed for polymorphism.
@@ -69,49 +71,44 @@ class SerializationMixin(BaseModel):
__is_polymorphic_base = False
__subclasses_map__ = {}
- @classmethod
- def __get_pydantic_core_schema__(
- cls, source: type["SerializationMixin"], handler: Callable[[Any], core_schema.CoreSchema]
- ) -> core_schema.CoreSchema:
- schema = handler(source)
- og_schema_ref = schema["ref"]
- schema["ref"] += ":mixin"
-
- return core_schema.no_info_before_validator_function(
- cls.__deserialize_with_real_type__,
- schema=schema,
- ref=og_schema_ref,
- serialization=core_schema.wrap_serializer_function_ser_schema(cls.__serialize_add_class_type__),
- )
-
- @classmethod
- def __serialize_add_class_type__(
- cls,
- value,
- handler: core_schema.SerializerFunctionWrapHandler,
- ) -> Any:
- ret = handler(value)
- if not len(cls.__subclasses__()):
- # only subclass add `__module_class_name`
- ret["__module_class_name"] = f"{cls.__module__}.{cls.__qualname__}"
+ @model_serializer(mode="wrap")
+ def __serialize_with_class_type__(self, default_serializer) -> Any:
+ # default serializer, then append the `__module_class_name` field and return
+ ret = default_serializer(self)
+ ret["__module_class_name"] = f"{self.__class__.__module__}.{self.__class__.__qualname__}"
return ret
+ @model_validator(mode="wrap")
@classmethod
- def __deserialize_with_real_type__(cls, value: Any):
- if not isinstance(value, dict):
- return value
+ def __convert_to_real_type__(cls, value: Any, handler):
+ if isinstance(value, dict) is False:
+ return handler(value)
- if not cls.__is_polymorphic_base or (len(cls.__subclasses__()) and "__module_class_name" not in value):
- # add right condition to init BaseClass like Action()
- return value
- module_class_name = value.get("__module_class_name", None)
- if module_class_name is None:
- raise ValueError("Missing field: __module_class_name")
+ # it is a dict so make sure to remove the __module_class_name
+ # because we don't allow extra keywords but want to ensure
+ # e.g Cat.model_validate(cat.model_dump()) works
+ class_full_name = value.pop("__module_class_name", None)
- class_type = cls.__subclasses_map__.get(module_class_name, None)
+ # if it's not the polymorphic base we construct via default handler
+ if not cls.__is_polymorphic_base:
+ if class_full_name is None:
+ return handler(value)
+ elif str(cls) == f"":
+ return handler(value)
+ else:
+ # f"Trying to instantiate {class_full_name} but this is not the polymorphic base class")
+ pass
+
+ # otherwise we lookup the correct polymorphic type and construct that
+ # instead
+ if class_full_name is None:
+ raise ValueError("Missing __module_class_name field")
+
+ class_type = cls.__subclasses_map__.get(class_full_name, None)
if class_type is None:
- raise TypeError("Trying to instantiate {module_class_name} which not defined yet.")
+ # TODO could try dynamic import
+ raise TypeError("Trying to instantiate {class_full_name}, which has not yet been defined!")
return class_type(**value)
@@ -151,12 +148,6 @@ class Document(BaseModel):
"""
return os.path.join(self.root_path, self.filename)
- @property
- def full_path(self):
- if not CONFIG.git_repo:
- return None
- return str(CONFIG.git_repo.workdir / self.root_path / self.filename)
-
def __str__(self):
return self.content
@@ -173,6 +164,26 @@ class Documents(BaseModel):
docs: Dict[str, Document] = Field(default_factory=dict)
+ @classmethod
+ def from_iterable(cls, documents: Iterable[Document]) -> Documents:
+ """Create a Documents instance from a list of Document instances.
+
+ :param documents: A list of Document instances.
+ :return: A Documents instance.
+ """
+
+ docs = {doc.filename: doc for doc in documents}
+ return Documents(docs=docs)
+
+ def to_action_output(self) -> "ActionOutput":
+ """Convert to action output string.
+
+ :return: A string representing action output.
+ """
+ from metagpt.actions.action_output import ActionOutput
+
+ return ActionOutput(content=self.model_dump_json(), instruct_content=self)
+
class Message(BaseModel):
"""list[: ]"""
@@ -193,12 +204,17 @@ class Message(BaseModel):
@field_validator("instruct_content", mode="before")
@classmethod
def check_instruct_content(cls, ic: Any) -> BaseModel:
- if ic and not isinstance(ic, BaseModel) and "class" in ic:
- # compatible with custom-defined ActionOutput
- mapping = actionoutput_str_to_mapping(ic["mapping"])
-
- actionnode_class = import_class("ActionNode", "metagpt.actions.action_node") # avoid circular import
- ic_obj = actionnode_class.create_model_class(class_name=ic["class"], mapping=mapping)
+ if ic and isinstance(ic, dict) and "class" in ic:
+ if "mapping" in ic:
+ # compatible with custom-defined ActionOutput
+ mapping = actionoutput_str_to_mapping(ic["mapping"])
+ actionnode_class = import_class("ActionNode", "metagpt.actions.action_node") # avoid circular import
+ ic_obj = actionnode_class.create_model_class(class_name=ic["class"], mapping=mapping)
+ elif "module" in ic:
+ # subclasses of BaseModel
+ ic_obj = import_class(ic["class"], ic["module"])
+ else:
+ raise KeyError("missing required key to init Message.instruct_content from dict")
ic = ic_obj(**ic["value"])
return ic
@@ -218,18 +234,21 @@ class Message(BaseModel):
return any_to_str_set(send_to if send_to else {MESSAGE_ROUTE_TO_ALL})
@field_serializer("instruct_content", mode="plain")
- def ser_instruct_content(self, ic: BaseModel) -> Union[str, None]:
+ def ser_instruct_content(self, ic: BaseModel) -> Union[dict, None]:
ic_dict = None
if ic:
# compatible with custom-defined ActionOutput
schema = ic.model_json_schema()
- # `Documents` contain definitions
- if "definitions" not in schema:
- # TODO refine with nested BaseModel
+ ic_type = str(type(ic))
+ if " CodePlanAndChangeContext:
+ ctx = CodePlanAndChangeContext(requirement=kwargs.get("requirement", ""))
+ for filename in filenames:
+ filename = Path(filename)
+ if filename.is_relative_to(PRDS_FILE_REPO):
+ ctx.prd_filename = filename.name
+ continue
+ if filename.is_relative_to(SYSTEM_DESIGN_FILE_REPO):
+ ctx.design_filename = filename.name
+ continue
+ if filename.is_relative_to(TASK_FILE_REPO):
+ ctx.task_filename = filename.name
+ continue
+ return ctx
+
+
# mermaid class view
class ClassMeta(BaseModel):
name: str = ""
diff --git a/metagpt/startup.py b/metagpt/startup.py
index 767a19a9d..4a077cab7 100644
--- a/metagpt/startup.py
+++ b/metagpt/startup.py
@@ -1,18 +1,80 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
+
import asyncio
+import shutil
from pathlib import Path
import typer
-from metagpt.config import CONFIG
+from metagpt.config2 import config
+from metagpt.const import CONFIG_ROOT, METAGPT_ROOT
+from metagpt.context import Context
+from metagpt.utils.project_repo import ProjectRepo
-app = typer.Typer(add_completion=False)
+app = typer.Typer(add_completion=False, pretty_exceptions_show_locals=False)
-@app.command()
+def generate_repo(
+ idea,
+ investment,
+ n_round,
+ code_review,
+ run_tests,
+ implement,
+ project_name,
+ inc,
+ project_path,
+ reqa_file,
+ max_auto_summarize_code,
+ recover_path,
+) -> ProjectRepo:
+ """Run the startup logic. Can be called from CLI or other Python scripts."""
+ from metagpt.roles import (
+ Architect,
+ Engineer,
+ ProductManager,
+ ProjectManager,
+ QaEngineer,
+ )
+ from metagpt.team import Team
+
+ config.update_via_cli(project_path, project_name, inc, reqa_file, max_auto_summarize_code)
+ ctx = Context(config=config)
+
+ if not recover_path:
+ company = Team(context=ctx)
+ company.hire(
+ [
+ ProductManager(),
+ Architect(),
+ ProjectManager(),
+ ]
+ )
+
+ if implement or code_review:
+ company.hire([Engineer(n_borg=5, use_code_review=code_review)])
+
+ if run_tests:
+ company.hire([QaEngineer()])
+ else:
+ stg_path = Path(recover_path)
+ if not stg_path.exists() or not str(stg_path).endswith("team"):
+ raise FileNotFoundError(f"{recover_path} not exists or not endswith `team`")
+
+ company = Team.deserialize(stg_path=stg_path, context=ctx)
+ idea = company.idea
+
+ company.invest(investment)
+ company.run_project(idea)
+ asyncio.run(company.run(n_round=n_round))
+
+ return ctx.repo
+
+
+@app.command("", help="Start a new project.")
def startup(
- idea: str = typer.Argument(..., help="Your innovative idea, such as 'Create a 2048 game.'"),
+ idea: str = typer.Argument(None, help="Your innovative idea, such as 'Create a 2048 game.'"),
investment: float = typer.Option(default=3.0, help="Dollar amount to invest in the AI company."),
n_round: int = typer.Option(default=5, help="Number of rounds for the simulation."),
code_review: bool = typer.Option(default=True, help="Whether to use code review."),
@@ -33,46 +95,49 @@ def startup(
"unlimited. This parameter is used for debugging the workflow.",
),
recover_path: str = typer.Option(default=None, help="recover the project from existing serialized storage"),
+ init_config: bool = typer.Option(default=False, help="Initialize the configuration file for MetaGPT."),
):
"""Run a startup. Be a boss."""
- from metagpt.roles import (
- Architect,
- Engineer,
- ProductManager,
- ProjectManager,
- QaEngineer,
+ if init_config:
+ copy_config_to()
+ return
+
+ if idea is None:
+ typer.echo("Missing argument 'IDEA'. Run 'metagpt --help' for more information.")
+ raise typer.Exit()
+
+ return generate_repo(
+ idea,
+ investment,
+ n_round,
+ code_review,
+ run_tests,
+ implement,
+ project_name,
+ inc,
+ project_path,
+ reqa_file,
+ max_auto_summarize_code,
+ recover_path,
)
- from metagpt.team import Team
- CONFIG.update_via_cli(project_path, project_name, inc, reqa_file, max_auto_summarize_code)
- if not recover_path:
- company = Team()
- company.hire(
- [
- ProductManager(),
- Architect(),
- ProjectManager(),
- ]
- )
+def copy_config_to(config_path=METAGPT_ROOT / "config" / "config2.yaml"):
+ """Initialize the configuration file for MetaGPT."""
+ target_path = CONFIG_ROOT / "config2.yaml"
- if implement or code_review:
- company.hire([Engineer(n_borg=5, use_code_review=code_review)])
+ # 创建目标目录(如果不存在)
+ target_path.parent.mkdir(parents=True, exist_ok=True)
- if run_tests:
- company.hire([QaEngineer()])
- else:
- # # stg_path = SERDESER_PATH.joinpath("team")
- stg_path = Path(recover_path)
- if not stg_path.exists() or not str(stg_path).endswith("team"):
- raise FileNotFoundError(f"{recover_path} not exists or not endswith `team`")
+ # 如果目标文件已经存在,则重命名为 .bak
+ if target_path.exists():
+ backup_path = target_path.with_suffix(".bak")
+ target_path.rename(backup_path)
+ print(f"Existing configuration file backed up at {backup_path}")
- company = Team.deserialize(stg_path=stg_path)
- idea = company.idea # use original idea
-
- company.invest(investment)
- company.run_project(idea)
- asyncio.run(company.run(n_round=n_round))
+ # 复制文件
+ shutil.copy(str(config_path), target_path)
+ print(f"Configuration file initialized at {target_path}")
if __name__ == "__main__":
diff --git a/metagpt/subscription.py b/metagpt/subscription.py
index e2b0916ac..d225a5d87 100644
--- a/metagpt/subscription.py
+++ b/metagpt/subscription.py
@@ -13,7 +13,7 @@ class SubscriptionRunner(BaseModel):
Example:
>>> import asyncio
- >>> from metagpt.subscription import SubscriptionRunner
+ >>> from metagpt.address import SubscriptionRunner
>>> from metagpt.roles import Searcher
>>> from metagpt.schema import Message
diff --git a/metagpt/team.py b/metagpt/team.py
index b98fc2efb..35f987b57 100644
--- a/metagpt/team.py
+++ b/metagpt/team.py
@@ -10,13 +10,13 @@
import warnings
from pathlib import Path
-from typing import Any
+from typing import Any, Optional
from pydantic import BaseModel, ConfigDict, Field
from metagpt.actions import UserRequirement
-from metagpt.config import CONFIG
from metagpt.const import MESSAGE_ROUTE_TO_ALL, SERDESER_PATH
+from metagpt.context import Context
from metagpt.environment import Environment
from metagpt.logs import logger
from metagpt.roles import Role
@@ -37,12 +37,17 @@ class Team(BaseModel):
model_config = ConfigDict(arbitrary_types_allowed=True)
- env: Environment = Field(default_factory=Environment)
+ env: Optional[Environment] = None
investment: float = Field(default=10.0)
idea: str = Field(default="")
- def __init__(self, **data: Any):
+ def __init__(self, context: Context = None, **data: Any):
super(Team, self).__init__(**data)
+ ctx = context or Context()
+ if not self.env:
+ self.env = Environment(context=ctx)
+ else:
+ self.env.context = ctx # The `env` object is allocated by deserialization
if "roles" in data:
self.hire(data["roles"])
if "env_desc" in data:
@@ -50,47 +55,43 @@ class Team(BaseModel):
def serialize(self, stg_path: Path = None):
stg_path = SERDESER_PATH.joinpath("team") if stg_path is None else stg_path
+ team_info_path = stg_path.joinpath("team.json")
- team_info_path = stg_path.joinpath("team_info.json")
- write_json_file(team_info_path, self.model_dump(exclude={"env": True}))
-
- self.env.serialize(stg_path.joinpath("environment")) # save environment alone
+ write_json_file(team_info_path, self.model_dump())
@classmethod
- def deserialize(cls, stg_path: Path) -> "Team":
+ def deserialize(cls, stg_path: Path, context: Context = None) -> "Team":
"""stg_path = ./storage/team"""
# recover team_info
- team_info_path = stg_path.joinpath("team_info.json")
+ team_info_path = stg_path.joinpath("team.json")
if not team_info_path.exists():
raise FileNotFoundError(
- "recover storage meta file `team_info.json` not exist, "
- "not to recover and please start a new project."
+ "recover storage meta file `team.json` not exist, " "not to recover and please start a new project."
)
team_info: dict = read_json_file(team_info_path)
-
- # recover environment
- environment = Environment.deserialize(stg_path=stg_path.joinpath("environment"))
- team_info.update({"env": environment})
- team = Team(**team_info)
+ ctx = context or Context()
+ team = Team(**team_info, context=ctx)
return team
def hire(self, roles: list[Role]):
"""Hire roles to cooperate"""
self.env.add_roles(roles)
+ @property
+ def cost_manager(self):
+ """Get cost manager"""
+ return self.env.context.cost_manager
+
def invest(self, investment: float):
"""Invest company. raise NoMoneyException when exceed max_budget."""
self.investment = investment
- CONFIG.max_budget = investment
+ self.cost_manager.max_budget = investment
logger.info(f"Investment: ${investment}.")
- @staticmethod
- def _check_balance():
- if CONFIG.cost_manager.total_cost > CONFIG.cost_manager.max_budget:
- raise NoMoneyException(
- CONFIG.cost_manager.total_cost, f"Insufficient funds: {CONFIG.cost_manager.max_budget}"
- )
+ def _check_balance(self):
+ if self.cost_manager.total_cost >= self.cost_manager.max_budget:
+ raise NoMoneyException(self.cost_manager.total_cost, f"Insufficient funds: {self.cost_manager.max_budget}")
def run_project(self, idea, send_to: str = ""):
"""Run a project from publishing user requirement."""
diff --git a/metagpt/tools/azure_tts.py b/metagpt/tools/azure_tts.py
index f4f8aa0a2..2e0e2267c 100644
--- a/metagpt/tools/azure_tts.py
+++ b/metagpt/tools/azure_tts.py
@@ -13,7 +13,6 @@ from uuid import uuid4
import aiofiles
from azure.cognitiveservices.speech import AudioConfig, SpeechConfig, SpeechSynthesizer
-from metagpt.config import CONFIG
from metagpt.logs import logger
@@ -25,8 +24,8 @@ class AzureTTS:
:param subscription_key: key is used to access your Azure AI service API, see: `https://portal.azure.com/` > `Resource Management` > `Keys and Endpoint`
:param region: This is the location (or region) of your resource. You may need to use this field when making calls to this API.
"""
- self.subscription_key = subscription_key if subscription_key else CONFIG.AZURE_TTS_SUBSCRIPTION_KEY
- self.region = region if region else CONFIG.AZURE_TTS_REGION
+ self.subscription_key = subscription_key
+ self.region = region
# 参数参考:https://learn.microsoft.com/zh-cn/azure/cognitive-services/speech-service/language-support?tabs=tts#voice-styles-and-roles
async def synthesize_speech(self, lang, voice, text, output_file):
@@ -83,10 +82,6 @@ async def oas3_azsure_tts(text, lang="", voice="", style="", role="", subscripti
role = "Girl"
if not style:
style = "affectionate"
- if not subscription_key:
- subscription_key = CONFIG.AZURE_TTS_SUBSCRIPTION_KEY
- if not region:
- region = CONFIG.AZURE_TTS_REGION
xml_value = AzureTTS.role_style_text(role=role, style=style, text=text)
tts = AzureTTS(subscription_key=subscription_key, region=region)
diff --git a/metagpt/tools/iflytek_tts.py b/metagpt/tools/iflytek_tts.py
index ad2395362..6ce48826b 100644
--- a/metagpt/tools/iflytek_tts.py
+++ b/metagpt/tools/iflytek_tts.py
@@ -23,7 +23,6 @@ import aiofiles
import websockets as websockets
from pydantic import BaseModel
-from metagpt.config import CONFIG
from metagpt.logs import logger
@@ -56,9 +55,9 @@ class IFlyTekTTS(object):
:param api_key: WebAPI argument, see: `https://console.xfyun.cn/services/tts`
:param api_secret: WebAPI argument, see: `https://console.xfyun.cn/services/tts`
"""
- self.app_id = app_id or CONFIG.IFLYTEK_APP_ID
- self.api_key = api_key or CONFIG.IFLYTEK_API_KEY
- self.api_secret = api_secret or CONFIG.API_SECRET
+ self.app_id = app_id
+ self.api_key = api_key
+ self.api_secret = api_secret
async def synthesize_speech(self, text, output_file: str, voice=DEFAULT_IFLYTEK_VOICE):
url = self._create_url()
@@ -127,14 +126,6 @@ async def oas3_iflytek_tts(text: str, voice: str = "", app_id: str = "", api_key
:return: Returns the Base64-encoded .mp3 file data if successful, otherwise an empty string.
"""
- if not app_id:
- app_id = CONFIG.IFLYTEK_APP_ID
- if not api_key:
- api_key = CONFIG.IFLYTEK_API_KEY
- if not api_secret:
- api_secret = CONFIG.IFLYTEK_API_SECRET
- if not voice:
- voice = CONFIG.IFLYTEK_VOICE or DEFAULT_IFLYTEK_VOICE
filename = Path(__file__).parent / (uuid.uuid4().hex + ".mp3")
try:
diff --git a/metagpt/tools/libs/gpt_v_generator.py b/metagpt/tools/libs/gpt_v_generator.py
index adc3b1051..e079a8eef 100644
--- a/metagpt/tools/libs/gpt_v_generator.py
+++ b/metagpt/tools/libs/gpt_v_generator.py
@@ -33,12 +33,12 @@ Now, please generate the corresponding webpage code including HTML, CSS and Java
@register_tool(tool_type=ToolTypeEnum.IMAGE2WEBPAGE.value)
class GPTvGenerator:
def __init__(self):
- from metagpt.config import CONFIG
+ from metagpt.config2 import config
- OPENAI_API_BASE = CONFIG.OPENAI_BASE_URL
- API_KEY = CONFIG.OPENAI_API_KEY
- MODEL = CONFIG.OPENAI_VISION_MODEL
- MAX_TOKENS = CONFIG.VISION_MAX_TOKENS
+ OPENAI_API_BASE = config.llm.base_url
+ API_KEY = config.llm.api_key
+ MODEL = config.OPENAI_VISION_MODEL
+ MAX_TOKENS = config.VISION_MAX_TOKENS
self.api_key = API_KEY
self.api_base = OPENAI_API_BASE
self.model = MODEL
diff --git a/metagpt/tools/libs/sd_engine.py b/metagpt/tools/libs/sd_engine.py
index 7f182f380..57a025f3c 100644
--- a/metagpt/tools/libs/sd_engine.py
+++ b/metagpt/tools/libs/sd_engine.py
@@ -56,11 +56,11 @@ default_negative_prompt = "(easynegative:0.8),black, dark,Low resolution"
@register_tool(tool_type=ToolTypeEnum.STABLE_DIFFUSION.value)
class SDEngine:
def __init__(self, sd_url=""):
- from metagpt.config import CONFIG
+ from metagpt.config2 import config
# Initialize the SDEngine with configuration
- self.sd_url = sd_url if sd_url else CONFIG.get("SD_URL")
- self.sd_t2i_url = f"{self.sd_url}{CONFIG.get('SD_T2I_API')}"
+ self.sd_url = sd_url if sd_url else config.get("SD_URL")
+ self.sd_t2i_url = f"{self.sd_url}{config.get('SD_T2I_API')}"
# Define default payload settings for SD API
self.payload = payload
logger.info(self.sd_t2i_url)
diff --git a/metagpt/tools/libs/web_scraping.py b/metagpt/tools/libs/web_scraping.py
index e8e73f123..921fca809 100644
--- a/metagpt/tools/libs/web_scraping.py
+++ b/metagpt/tools/libs/web_scraping.py
@@ -19,4 +19,4 @@ async def scrape_web_playwright(url, *urls):
web = await PlaywrightWrapper("chromium").run(url, *urls)
# Return the inner text content of the web page
- return {"inner_text": web.inner_text, "html": web.html}
+ return {"inner_text": web.inner_text.strip(), "html": web.html.strip()}
diff --git a/metagpt/tools/metagpt_text_to_image.py b/metagpt/tools/metagpt_text_to_image.py
index 9a84e69eb..cf7bf97e7 100644
--- a/metagpt/tools/metagpt_text_to_image.py
+++ b/metagpt/tools/metagpt_text_to_image.py
@@ -13,7 +13,6 @@ import aiohttp
import requests
from pydantic import BaseModel
-from metagpt.config import CONFIG
from metagpt.logs import logger
@@ -22,7 +21,7 @@ class MetaGPTText2Image:
"""
:param model_url: Model reset api url
"""
- self.model_url = model_url if model_url else CONFIG.METAGPT_TEXT_TO_IMAGE_MODEL
+ self.model_url = model_url
async def text_2_image(self, text, size_type="512x512"):
"""Text to image
@@ -93,6 +92,4 @@ async def oas3_metagpt_text_to_image(text, size_type: str = "512x512", model_url
"""
if not text:
return ""
- if not model_url:
- model_url = CONFIG.METAGPT_TEXT_TO_IMAGE_MODEL_URL
return await MetaGPTText2Image(model_url).text_2_image(text, size_type=size_type)
diff --git a/metagpt/tools/moderation.py b/metagpt/tools/moderation.py
index cda164ec5..f00b0e1f2 100644
--- a/metagpt/tools/moderation.py
+++ b/metagpt/tools/moderation.py
@@ -7,12 +7,12 @@
"""
from typing import Union
-from metagpt.llm import LLM
+from metagpt.provider.base_llm import BaseLLM
class Moderation:
- def __init__(self):
- self.llm = LLM()
+ def __init__(self, llm: BaseLLM):
+ self.llm = llm
def handle_moderation_results(self, results):
resp = []
diff --git a/metagpt/tools/openai_text_to_embedding.py b/metagpt/tools/openai_text_to_embedding.py
index 52b2cc9eb..e93bfb271 100644
--- a/metagpt/tools/openai_text_to_embedding.py
+++ b/metagpt/tools/openai_text_to_embedding.py
@@ -13,7 +13,6 @@ import aiohttp
import requests
from pydantic import BaseModel, Field
-from metagpt.config import CONFIG
from metagpt.logs import logger
@@ -43,11 +42,12 @@ class ResultEmbedding(BaseModel):
class OpenAIText2Embedding:
- def __init__(self, openai_api_key):
+ def __init__(self, api_key: str, proxy: str):
"""
:param openai_api_key: OpenAI API key, For more details, checkout: `https://platform.openai.com/account/api-keys`
"""
- self.openai_api_key = openai_api_key or CONFIG.OPENAI_API_KEY
+ self.api_key = api_key
+ self.proxy = proxy
async def text_2_embedding(self, text, model="text-embedding-ada-002"):
"""Text to embedding
@@ -57,8 +57,8 @@ class OpenAIText2Embedding:
:return: A json object of :class:`ResultEmbedding` class if successful, otherwise `{}`.
"""
- proxies = {"proxy": CONFIG.openai_proxy} if CONFIG.openai_proxy else {}
- headers = {"Content-Type": "application/json", "Authorization": f"Bearer {self.openai_api_key}"}
+ proxies = {"proxy": self.proxy} if self.proxy else {}
+ headers = {"Content-Type": "application/json", "Authorization": f"Bearer {self.api_key}"}
data = {"input": text, "model": model}
url = "https://api.openai.com/v1/embeddings"
try:
@@ -72,16 +72,14 @@ class OpenAIText2Embedding:
# Export
-async def oas3_openai_text_to_embedding(text, model="text-embedding-ada-002", openai_api_key=""):
+async def oas3_openai_text_to_embedding(text, openai_api_key: str, model="text-embedding-ada-002", proxy: str = ""):
"""Text to embedding
:param text: The text used for embedding.
:param model: One of ['text-embedding-ada-002'], ID of the model to use. For more details, checkout: `https://api.openai.com/v1/models`.
- :param openai_api_key: OpenAI API key, For more details, checkout: `https://platform.openai.com/account/api-keys`
+ :param config: OpenAI config with API key, For more details, checkout: `https://platform.openai.com/account/api-keys`
:return: A json object of :class:`ResultEmbedding` class if successful, otherwise `{}`.
"""
if not text:
return ""
- if not openai_api_key:
- openai_api_key = CONFIG.OPENAI_API_KEY
- return await OpenAIText2Embedding(openai_api_key).text_2_embedding(text, model=model)
+ return await OpenAIText2Embedding(api_key=openai_api_key, proxy=proxy).text_2_embedding(text, model=model)
diff --git a/metagpt/tools/openai_text_to_image.py b/metagpt/tools/openai_text_to_image.py
index aa00abdcc..bf7c5e799 100644
--- a/metagpt/tools/openai_text_to_image.py
+++ b/metagpt/tools/openai_text_to_image.py
@@ -10,16 +10,13 @@
import aiohttp
import requests
-from metagpt.llm import LLM
from metagpt.logs import logger
+from metagpt.provider.base_llm import BaseLLM
class OpenAIText2Image:
- def __init__(self):
- """
- :param openai_api_key: OpenAI API key, For more details, checkout: `https://platform.openai.com/account/api-keys`
- """
- self._llm = LLM()
+ def __init__(self, llm: BaseLLM):
+ self.llm = llm
async def text_2_image(self, text, size_type="1024x1024"):
"""Text to image
@@ -29,7 +26,7 @@ class OpenAIText2Image:
:return: The image data is returned in Base64 encoding.
"""
try:
- result = await self._llm.aclient.images.generate(prompt=text, n=1, size=size_type)
+ result = await self.llm.aclient.images.generate(prompt=text, n=1, size=size_type)
except Exception as e:
logger.error(f"An error occurred:{e}")
return ""
@@ -57,13 +54,14 @@ class OpenAIText2Image:
# Export
-async def oas3_openai_text_to_image(text, size_type: str = "1024x1024"):
+async def oas3_openai_text_to_image(text, size_type: str = "1024x1024", llm: BaseLLM = None):
"""Text to image
:param text: The text used for image conversion.
:param size_type: One of ['256x256', '512x512', '1024x1024']
+ :param llm: LLM instance
:return: The image data is returned in Base64 encoding.
"""
if not text:
return ""
- return await OpenAIText2Image().text_2_image(text, size_type=size_type)
+ return await OpenAIText2Image(llm).text_2_image(text, size_type=size_type)
diff --git a/metagpt/tools/search_engine.py b/metagpt/tools/search_engine.py
index 64388a11f..0d0db9147 100644
--- a/metagpt/tools/search_engine.py
+++ b/metagpt/tools/search_engine.py
@@ -10,7 +10,6 @@ from typing import Callable, Coroutine, Literal, Optional, Union, overload
from semantic_kernel.skill_definition import sk_function
-from metagpt.config import CONFIG
from metagpt.tools import SearchEngineType
@@ -43,22 +42,22 @@ class SearchEngine:
def __init__(
self,
- engine: Optional[SearchEngineType] = None,
+ engine: Optional[SearchEngineType] = SearchEngineType.SERPER_GOOGLE,
run_func: Callable[[str, int, bool], Coroutine[None, None, Union[str, list[str]]]] = None,
+ **kwargs,
):
- engine = engine or CONFIG.search_engine
if engine == SearchEngineType.SERPAPI_GOOGLE:
module = "metagpt.tools.search_engine_serpapi"
- run_func = importlib.import_module(module).SerpAPIWrapper().run
+ run_func = importlib.import_module(module).SerpAPIWrapper(**kwargs).run
elif engine == SearchEngineType.SERPER_GOOGLE:
module = "metagpt.tools.search_engine_serper"
- run_func = importlib.import_module(module).SerperWrapper().run
+ run_func = importlib.import_module(module).SerperWrapper(**kwargs).run
elif engine == SearchEngineType.DIRECT_GOOGLE:
module = "metagpt.tools.search_engine_googleapi"
- run_func = importlib.import_module(module).GoogleAPIWrapper().run
+ run_func = importlib.import_module(module).GoogleAPIWrapper(**kwargs).run
elif engine == SearchEngineType.DUCK_DUCK_GO:
module = "metagpt.tools.search_engine_ddg"
- run_func = importlib.import_module(module).DDGAPIWrapper().run
+ run_func = importlib.import_module(module).DDGAPIWrapper(**kwargs).run
elif engine == SearchEngineType.CUSTOM_ENGINE:
pass # run_func = run_func
else:
diff --git a/metagpt/tools/search_engine_ddg.py b/metagpt/tools/search_engine_ddg.py
index 57bc61b82..3d004a4ee 100644
--- a/metagpt/tools/search_engine_ddg.py
+++ b/metagpt/tools/search_engine_ddg.py
@@ -7,6 +7,8 @@ import json
from concurrent import futures
from typing import Literal, overload
+from metagpt.config2 import config
+
try:
from duckduckgo_search import DDGS
except ImportError:
@@ -15,8 +17,6 @@ except ImportError:
"You can install it by running the command: `pip install -e.[search-ddg]`"
)
-from metagpt.config import CONFIG
-
class DDGAPIWrapper:
"""Wrapper around duckduckgo_search API.
@@ -31,8 +31,8 @@ class DDGAPIWrapper:
executor: futures.Executor | None = None,
):
kwargs = {}
- if CONFIG.global_proxy:
- kwargs["proxies"] = CONFIG.global_proxy
+ if config.proxy:
+ kwargs["proxies"] = config.proxy
self.loop = loop
self.executor = executor
self.ddgs = DDGS(**kwargs)
diff --git a/metagpt/tools/search_engine_googleapi.py b/metagpt/tools/search_engine_googleapi.py
index 8aca3aee2..0a8f796cb 100644
--- a/metagpt/tools/search_engine_googleapi.py
+++ b/metagpt/tools/search_engine_googleapi.py
@@ -11,7 +11,7 @@ from urllib.parse import urlparse
import httplib2
from pydantic import BaseModel, ConfigDict, Field, field_validator
-from metagpt.config import CONFIG
+from metagpt.config2 import config
from metagpt.logs import logger
try:
@@ -35,7 +35,7 @@ class GoogleAPIWrapper(BaseModel):
@field_validator("google_api_key", mode="before")
@classmethod
def check_google_api_key(cls, val: str):
- val = val or CONFIG.google_api_key
+ val = val or config.search.api_key
if not val:
raise ValueError(
"To use, make sure you provide the google_api_key when constructing an object. Alternatively, "
@@ -47,7 +47,7 @@ class GoogleAPIWrapper(BaseModel):
@field_validator("google_cse_id", mode="before")
@classmethod
def check_google_cse_id(cls, val: str):
- val = val or CONFIG.google_cse_id
+ val = val or config.search.cse_id
if not val:
raise ValueError(
"To use, make sure you provide the google_cse_id when constructing an object. Alternatively, "
@@ -59,8 +59,8 @@ class GoogleAPIWrapper(BaseModel):
@property
def google_api_client(self):
build_kwargs = {"developerKey": self.google_api_key}
- if CONFIG.global_proxy:
- parse_result = urlparse(CONFIG.global_proxy)
+ if config.proxy:
+ parse_result = urlparse(config.proxy)
proxy_type = parse_result.scheme
if proxy_type == "https":
proxy_type = "http"
diff --git a/metagpt/tools/search_engine_serpapi.py b/metagpt/tools/search_engine_serpapi.py
index 9d2d20af6..a8d5b49d0 100644
--- a/metagpt/tools/search_engine_serpapi.py
+++ b/metagpt/tools/search_engine_serpapi.py
@@ -10,7 +10,7 @@ from typing import Any, Dict, Optional, Tuple
import aiohttp
from pydantic import BaseModel, ConfigDict, Field, field_validator
-from metagpt.config import CONFIG
+from metagpt.config2 import config
class SerpAPIWrapper(BaseModel):
@@ -32,7 +32,7 @@ class SerpAPIWrapper(BaseModel):
@field_validator("serpapi_api_key", mode="before")
@classmethod
def check_serpapi_api_key(cls, val: str):
- val = val or CONFIG.serpapi_api_key
+ val = val or config.search.api_key
if not val:
raise ValueError(
"To use, make sure you provide the serpapi_api_key when constructing an object. Alternatively, "
diff --git a/metagpt/tools/search_engine_serper.py b/metagpt/tools/search_engine_serper.py
index 3dc1d3591..39cb936b8 100644
--- a/metagpt/tools/search_engine_serper.py
+++ b/metagpt/tools/search_engine_serper.py
@@ -11,7 +11,7 @@ from typing import Any, Dict, Optional, Tuple
import aiohttp
from pydantic import BaseModel, ConfigDict, Field, field_validator
-from metagpt.config import CONFIG
+from metagpt.config2 import config
class SerperWrapper(BaseModel):
@@ -25,7 +25,7 @@ class SerperWrapper(BaseModel):
@field_validator("serper_api_key", mode="before")
@classmethod
def check_serper_api_key(cls, val: str):
- val = val or CONFIG.serper_api_key
+ val = val or config.search.api_key
if not val:
raise ValueError(
"To use, make sure you provide the serper_api_key when constructing an object. Alternatively, "
diff --git a/metagpt/tools/tool_registry.py b/metagpt/tools/tool_registry.py
index d16defa0a..7e4ee5ead 100644
--- a/metagpt/tools/tool_registry.py
+++ b/metagpt/tools/tool_registry.py
@@ -24,9 +24,10 @@ class ToolRegistry(BaseModel):
tool_types: dict = {}
tools_by_types: dict = defaultdict(dict) # two-layer k-v, {tool_type: {tool_name: {...}, ...}, ...}
- def register_tool_type(self, tool_type: ToolType):
+ def register_tool_type(self, tool_type: ToolType, verbose: bool = False):
self.tool_types[tool_type.name] = tool_type
- logger.info(f"tool type {tool_type.name} registered")
+ if verbose:
+ logger.info(f"tool type {tool_type.name} registered")
def register_tool(
self,
@@ -38,6 +39,7 @@ class ToolRegistry(BaseModel):
tool_source_object=None,
include_functions=[],
make_schema_if_not_exists=True,
+ verbose=False,
):
if self.has_tool(tool_name):
return
@@ -68,7 +70,8 @@ class ToolRegistry(BaseModel):
tool = Tool(name=tool_name, path=tool_path, schemas=schemas, code=tool_code)
self.tools[tool_name] = tool
self.tools_by_types[tool_type][tool_name] = tool
- logger.info(f"{tool_name} registered")
+ if verbose:
+ logger.info(f"{tool_name} registered")
def has_tool(self, key: str) -> Tool:
return key in self.tools
diff --git a/metagpt/tools/ut_writer.py b/metagpt/tools/ut_writer.py
index f2f2bf51c..243871aff 100644
--- a/metagpt/tools/ut_writer.py
+++ b/metagpt/tools/ut_writer.py
@@ -4,6 +4,7 @@
import json
from pathlib import Path
+from metagpt.config2 import config
from metagpt.provider.openai_api import OpenAILLM as GPTAPI
from metagpt.utils.common import awrite
@@ -281,6 +282,6 @@ class UTGenerator:
"""Choose based on different calling methods"""
result = ""
if self.chatgpt_method == "API":
- result = await GPTAPI().aask_code(messages=messages)
+ result = await GPTAPI(config.get_openai_llm()).aask_code(messages=messages)
return result
diff --git a/metagpt/tools/web_browser_engine.py b/metagpt/tools/web_browser_engine.py
index abd84cc8d..411c1604b 100644
--- a/metagpt/tools/web_browser_engine.py
+++ b/metagpt/tools/web_browser_engine.py
@@ -1,14 +1,11 @@
#!/usr/bin/env python
-"""
-@Modified By: mashenquan, 2023/8/20. Remove global configuration `CONFIG`, enable configuration support for business isolation.
-"""
+# -*- coding: utf-8 -*-
from __future__ import annotations
import importlib
from typing import Any, Callable, Coroutine, overload
-from metagpt.config import CONFIG
from metagpt.tools import WebBrowserEngineType
from metagpt.utils.parse_html import WebPage
@@ -16,10 +13,9 @@ from metagpt.utils.parse_html import WebPage
class WebBrowserEngine:
def __init__(
self,
- engine: WebBrowserEngineType | None = None,
+ engine: WebBrowserEngineType = WebBrowserEngineType.PLAYWRIGHT,
run_func: Callable[..., Coroutine[Any, Any, WebPage | list[WebPage]]] | None = None,
):
- engine = engine or CONFIG.web_browser_engine
if engine is None:
raise NotImplementedError
diff --git a/metagpt/tools/web_browser_engine_playwright.py b/metagpt/tools/web_browser_engine_playwright.py
index 15c8a78d7..7c33da923 100644
--- a/metagpt/tools/web_browser_engine_playwright.py
+++ b/metagpt/tools/web_browser_engine_playwright.py
@@ -1,7 +1,5 @@
#!/usr/bin/env python
-"""
-@Modified By: mashenquan, 2023/8/20. Remove global configuration `CONFIG`, enable configuration support for business isolation.
-"""
+# -*- coding: utf-8 -*-
from __future__ import annotations
@@ -27,20 +25,20 @@ class PlaywrightWrapper:
def __init__(
self,
- browser_type: Literal["chromium", "firefox", "webkit"] | None = None,
+ browser_type: Literal["chromium", "firefox", "webkit"] | None = "chromium",
launch_kwargs: dict | None = None,
**kwargs,
) -> None:
- from metagpt.config import CONFIG
+ from metagpt.config2 import (
+ config, # avoid circular import error when importing tools"
+ )
- if browser_type is None:
- browser_type = CONFIG.playwright_browser_type
self.browser_type = browser_type
launch_kwargs = launch_kwargs or {}
- if CONFIG.global_proxy and "proxy" not in launch_kwargs:
+ if config.proxy and "proxy" not in launch_kwargs:
args = launch_kwargs.get("args", [])
if not any(str.startswith(i, "--proxy-server=") for i in args):
- launch_kwargs["proxy"] = {"server": CONFIG.global_proxy}
+ launch_kwargs["proxy"] = {"server": config.proxy}
self.launch_kwargs = launch_kwargs
context_kwargs = {}
if "ignore_https_errors" in kwargs:
@@ -80,8 +78,8 @@ class PlaywrightWrapper:
executable_path = Path(browser_type.executable_path)
if not executable_path.exists() and "executable_path" not in self.launch_kwargs:
kwargs = {}
- if CONFIG.global_proxy:
- kwargs["env"] = {"ALL_PROXY": CONFIG.global_proxy}
+ if config.proxy:
+ kwargs["env"] = {"ALL_PROXY": config.proxy}
await _install_browsers(self.browser_type, **kwargs)
if self._has_run_precheck:
diff --git a/metagpt/tools/web_browser_engine_selenium.py b/metagpt/tools/web_browser_engine_selenium.py
index 70b651935..02dd5c173 100644
--- a/metagpt/tools/web_browser_engine_selenium.py
+++ b/metagpt/tools/web_browser_engine_selenium.py
@@ -1,7 +1,5 @@
#!/usr/bin/env python
-"""
-@Modified By: mashenquan, 2023/8/20. Remove global configuration `CONFIG`, enable configuration support for business isolation.
-"""
+# -*- coding: utf-8 -*-
from __future__ import annotations
@@ -17,7 +15,7 @@ from selenium.webdriver.support.wait import WebDriverWait
from webdriver_manager.core.download_manager import WDMDownloadManager
from webdriver_manager.core.http import WDMHttpClient
-from metagpt.config import CONFIG
+from metagpt.config2 import config
from metagpt.utils.parse_html import WebPage
@@ -35,18 +33,16 @@ class SeleniumWrapper:
def __init__(
self,
- browser_type: Literal["chrome", "firefox", "edge", "ie"] | None = None,
+ browser_type: Literal["chrome", "firefox", "edge", "ie"] = "chrome",
launch_kwargs: dict | None = None,
*,
loop: asyncio.AbstractEventLoop | None = None,
executor: futures.Executor | None = None,
) -> None:
- if browser_type is None:
- browser_type = CONFIG.selenium_browser_type
self.browser_type = browser_type
launch_kwargs = launch_kwargs or {}
- if CONFIG.global_proxy and "proxy-server" not in launch_kwargs:
- launch_kwargs["proxy-server"] = CONFIG.global_proxy
+ if config.proxy and "proxy-server" not in launch_kwargs:
+ launch_kwargs["proxy-server"] = config.proxy
self.executable_path = launch_kwargs.pop("executable_path", None)
self.launch_args = [f"--{k}={v}" for k, v in launch_kwargs.items()]
@@ -97,8 +93,8 @@ _webdriver_manager_types = {
class WDMHttpProxyClient(WDMHttpClient):
def get(self, url, **kwargs):
- if "proxies" not in kwargs and CONFIG.global_proxy:
- kwargs["proxies"] = {"all_proxy": CONFIG.global_proxy}
+ if "proxies" not in kwargs and config.proxy:
+ kwargs["proxies"] = {"all_proxy": config.proxy}
return super().get(url, **kwargs)
diff --git a/metagpt/utils/common.py b/metagpt/utils/common.py
index ed73cb061..543c627a3 100644
--- a/metagpt/utils/common.py
+++ b/metagpt/utils/common.py
@@ -29,7 +29,7 @@ from typing import Any, List, Tuple, Union
import aiofiles
import loguru
from pydantic_core import to_jsonable_python
-from tenacity import RetryCallState, _utils
+from tenacity import RetryCallState, RetryError, _utils
from metagpt.const import MESSAGE_ROUTE_TO_ALL
from metagpt.logs import logger
@@ -407,12 +407,12 @@ def any_to_str_set(val) -> set:
return res
-def is_subscribed(message: "Message", tags: set):
+def is_send_to(message: "Message", addresses: set):
"""Return whether it's consumer"""
if MESSAGE_ROUTE_TO_ALL in message.send_to:
return True
- for i in tags:
+ for i in addresses:
if i in message.send_to:
return True
return False
@@ -531,7 +531,7 @@ def role_raise_decorator(func):
self.rc.memory.delete(self.latest_observed_msg)
# raise again to make it captured outside
raise Exception(format_trackback_info(limit=None))
- except Exception:
+ except Exception as e:
if self.latest_observed_msg:
logger.warning(
"There is a exception in role's execution, in order to resume, "
@@ -540,6 +540,12 @@ def role_raise_decorator(func):
# remove role newest observed msg to make it observed again
self.rc.memory.delete(self.latest_observed_msg)
# raise again to make it captured outside
+ if isinstance(e, RetryError):
+ last_error = e.last_attempt._exception
+ name = any_to_str(last_error)
+ if re.match(r"^openai\.", name) or re.match(r"^httpx\.", name):
+ raise last_error
+
raise Exception(format_trackback_info(limit=None))
return wrapper
diff --git a/metagpt/utils/cost_manager.py b/metagpt/utils/cost_manager.py
index ce53f2285..7bf5154b6 100644
--- a/metagpt/utils/cost_manager.py
+++ b/metagpt/utils/cost_manager.py
@@ -80,3 +80,20 @@ class CostManager(BaseModel):
def get_costs(self) -> Costs:
"""Get all costs"""
return Costs(self.total_prompt_tokens, self.total_completion_tokens, self.total_cost, self.total_budget)
+
+
+class TokenCostManager(CostManager):
+ """open llm model is self-host, it's free and without cost"""
+
+ def update_cost(self, prompt_tokens, completion_tokens, model):
+ """
+ Update the total cost, prompt tokens, and completion tokens.
+
+ Args:
+ prompt_tokens (int): The number of tokens used in the prompt.
+ completion_tokens (int): The number of tokens used in the completion.
+ model (str): The model used for the API call.
+ """
+ self.total_prompt_tokens += prompt_tokens
+ self.total_completion_tokens += completion_tokens
+ logger.info(f"prompt_tokens: {prompt_tokens}, completion_tokens: {completion_tokens}")
diff --git a/metagpt/utils/dependency_file.py b/metagpt/utils/dependency_file.py
index 7cf9a1d49..c8b3bc4a4 100644
--- a/metagpt/utils/dependency_file.py
+++ b/metagpt/utils/dependency_file.py
@@ -9,6 +9,7 @@
from __future__ import annotations
import json
+import re
from pathlib import Path
from typing import Set
@@ -36,7 +37,9 @@ class DependencyFile:
"""Load dependencies from the file asynchronously."""
if not self._filename.exists():
return
- self._dependencies = json.loads(await aread(self._filename))
+ json_data = await aread(self._filename)
+ json_data = re.sub(r"\\+", "/", json_data) # Compatible with windows path
+ self._dependencies = json.loads(json_data)
@handle_exception
async def save(self):
@@ -60,17 +63,20 @@ class DependencyFile:
key = Path(filename).relative_to(root)
except ValueError:
key = filename
-
+ skey = re.sub(r"\\+", "/", str(key)) # Compatible with windows path
if dependencies:
relative_paths = []
for i in dependencies:
try:
- relative_paths.append(str(Path(i).relative_to(root)))
+ s = str(Path(i).relative_to(root))
except ValueError:
- relative_paths.append(str(i))
- self._dependencies[str(key)] = relative_paths
- elif str(key) in self._dependencies:
- del self._dependencies[str(key)]
+ s = str(i)
+ s = re.sub(r"\\+", "/", s) # Compatible with windows path
+ relative_paths.append(s)
+
+ self._dependencies[skey] = relative_paths
+ elif skey in self._dependencies:
+ del self._dependencies[skey]
if persist:
await self.save()
diff --git a/metagpt/utils/embedding.py b/metagpt/utils/embedding.py
new file mode 100644
index 000000000..21d62948c
--- /dev/null
+++ b/metagpt/utils/embedding.py
@@ -0,0 +1,16 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+"""
+@Time : 2024/1/4 20:58
+@Author : alexanderwu
+@File : embedding.py
+"""
+from langchain_community.embeddings import OpenAIEmbeddings
+
+from metagpt.config2 import config
+
+
+def get_embedding():
+ llm = config.get_openai_llm()
+ embedding = OpenAIEmbeddings(openai_api_key=llm.api_key, openai_api_base=llm.base_url)
+ return embedding
diff --git a/metagpt/utils/file_repository.py b/metagpt/utils/file_repository.py
index 0ddca414d..d2a06963a 100644
--- a/metagpt/utils/file_repository.py
+++ b/metagpt/utils/file_repository.py
@@ -16,7 +16,6 @@ from typing import Dict, List, Set
import aiofiles
-from metagpt.config import CONFIG
from metagpt.logs import logger
from metagpt.schema import Document
from metagpt.utils.common import aread
@@ -46,7 +45,7 @@ class FileRepository:
# Initializing
self.workdir.mkdir(parents=True, exist_ok=True)
- async def save(self, filename: Path | str, content, dependencies: List[str] = None):
+ async def save(self, filename: Path | str, content, dependencies: List[str] = None) -> Document:
"""Save content to a file and update its dependencies.
:param filename: The filename or path within the repository.
@@ -55,6 +54,7 @@ class FileRepository:
"""
pathname = self.workdir / filename
pathname.parent.mkdir(parents=True, exist_ok=True)
+ content = content if content else "" # avoid `argument must be str, not None` to make it continue
async with aiofiles.open(str(pathname), mode="w") as writer:
await writer.write(content)
logger.info(f"save to: {str(pathname)}")
@@ -64,6 +64,8 @@ class FileRepository:
await dependency_file.update(pathname, set(dependencies))
logger.info(f"update dependency: {str(pathname)}:{dependencies}")
+ return Document(root_path=str(self._relative_path), filename=str(filename), content=content)
+
async def get_dependency(self, filename: Path | str) -> Set[str]:
"""Get the dependencies of a file.
@@ -99,21 +101,28 @@ class FileRepository:
path_name = self.workdir / filename
if not path_name.exists():
return None
+ if not path_name.is_file():
+ return None
doc.content = await aread(path_name)
return doc
- async def get_all(self) -> List[Document]:
+ async def get_all(self, filter_ignored=True) -> List[Document]:
"""Get the content of all files in the repository.
:return: List of Document instances representing files.
"""
docs = []
- for root, dirs, files in os.walk(str(self.workdir)):
- for file in files:
- file_path = Path(root) / file
- relative_path = file_path.relative_to(self.workdir)
- doc = await self.get(relative_path)
+ if filter_ignored:
+ for f in self.all_files:
+ doc = await self.get(f)
docs.append(doc)
+ else:
+ for root, dirs, files in os.walk(str(self.workdir)):
+ for file in files:
+ file_path = Path(root) / file
+ relative_path = file_path.relative_to(self.workdir)
+ doc = await self.get(relative_path)
+ docs.append(doc)
return docs
@property
@@ -182,10 +191,20 @@ class FileRepository:
"""
current_time = datetime.now().strftime("%Y%m%d%H%M%S")
return current_time
- # guid_suffix = str(uuid.uuid4())[:8]
- # return f"{current_time}x{guid_suffix}"
- async def save_doc(self, doc: Document, with_suffix: str = None, dependencies: List[str] = None):
+ async def save_doc(self, doc: Document, dependencies: List[str] = None):
+ """Save content to a file and update its dependencies.
+
+ :param doc: The Document instance to be saved.
+ :type doc: Document
+ :param dependencies: A list of dependencies for the saved file.
+ :type dependencies: List[str], optional
+ """
+
+ await self.save(filename=doc.filename, content=doc.content, dependencies=dependencies)
+ logger.debug(f"File Saved: {str(doc.filename)}")
+
+ async def save_pdf(self, doc: Document, with_suffix: str = ".md", dependencies: List[str] = None):
"""Save a Document instance as a PDF file.
This method converts the content of the Document instance to Markdown,
@@ -203,70 +222,6 @@ class FileRepository:
await self.save(filename=str(filename), content=json_to_markdown(m), dependencies=dependencies)
logger.debug(f"File Saved: {str(filename)}")
- @staticmethod
- async def get_file(filename: Path | str, relative_path: Path | str = ".") -> Document | None:
- """Retrieve a specific file from the file repository.
-
- :param filename: The name or path of the file to retrieve.
- :type filename: Path or str
- :param relative_path: The relative path within the file repository.
- :type relative_path: Path or str, optional
- :return: The document representing the file, or None if not found.
- :rtype: Document or None
- """
- file_repo = CONFIG.git_repo.new_file_repository(relative_path=relative_path)
- return await file_repo.get(filename=filename)
-
- @staticmethod
- async def get_all_files(relative_path: Path | str = ".") -> List[Document]:
- """Retrieve all files from the file repository.
-
- :param relative_path: The relative path within the file repository.
- :type relative_path: Path or str, optional
- :return: A list of documents representing all files in the repository.
- :rtype: List[Document]
- """
- file_repo = CONFIG.git_repo.new_file_repository(relative_path=relative_path)
- return await file_repo.get_all()
-
- @staticmethod
- async def save_file(filename: Path | str, content, dependencies: List[str] = None, relative_path: Path | str = "."):
- """Save a file to the file repository.
-
- :param filename: The name or path of the file to save.
- :type filename: Path or str
- :param content: The content of the file.
- :param dependencies: A list of dependencies for the file.
- :type dependencies: List[str], optional
- :param relative_path: The relative path within the file repository.
- :type relative_path: Path or str, optional
- """
- file_repo = CONFIG.git_repo.new_file_repository(relative_path=relative_path)
- return await file_repo.save(filename=filename, content=content, dependencies=dependencies)
-
- @staticmethod
- async def save_as(
- doc: Document, with_suffix: str = None, dependencies: List[str] = None, relative_path: Path | str = "."
- ):
- """Save a Document instance with optional modifications.
-
- This static method creates a new FileRepository, saves the Document instance
- with optional modifications (such as a suffix), and logs the saved file.
-
- :param doc: The Document instance to be saved.
- :type doc: Document
- :param with_suffix: An optional suffix to append to the saved file's name.
- :type with_suffix: str, optional
- :param dependencies: A list of dependencies for the saved file.
- :type dependencies: List[str], optional
- :param relative_path: The relative path within the file repository.
- :type relative_path: Path or str, optional
- :return: A boolean indicating whether the save operation was successful.
- :rtype: bool
- """
- file_repo = CONFIG.git_repo.new_file_repository(relative_path=relative_path)
- return await file_repo.save_doc(doc=doc, with_suffix=with_suffix, dependencies=dependencies)
-
async def delete(self, filename: Path | str):
"""Delete a file from the file repository.
@@ -283,8 +238,3 @@ class FileRepository:
dependency_file = await self._git_repo.get_dependency()
await dependency_file.update(filename=pathname, dependencies=None)
logger.info(f"remove dependency key: {str(pathname)}")
-
- @staticmethod
- async def delete_file(filename: Path | str, relative_path: Path | str = "."):
- file_repo = CONFIG.git_repo.new_file_repository(relative_path=relative_path)
- await file_repo.delete(filename=filename)
diff --git a/metagpt/utils/git_repository.py b/metagpt/utils/git_repository.py
index e9855df05..16f675175 100644
--- a/metagpt/utils/git_repository.py
+++ b/metagpt/utils/git_repository.py
@@ -107,7 +107,10 @@ class GitRepository:
def delete_repository(self):
"""Delete the entire repository directory."""
if self.is_valid:
- shutil.rmtree(self._repository.working_dir)
+ try:
+ shutil.rmtree(self._repository.working_dir)
+ except Exception as e:
+ logger.exception(f"Failed delete git repo:{self.workdir}, error:{e}")
@property
def changed_files(self) -> Dict[str, str]:
@@ -198,11 +201,21 @@ class GitRepository:
new_path = self.workdir.parent / new_dir_name
if new_path.exists():
logger.info(f"Delete directory {str(new_path)}")
- shutil.rmtree(new_path)
+ try:
+ shutil.rmtree(new_path)
+ except Exception as e:
+ logger.warning(f"rm {str(new_path)} error: {e}")
+ if new_path.exists(): # Recheck for windows os
+ logger.warning(f"Failed to delete directory {str(new_path)}")
+ return
try:
shutil.move(src=str(self.workdir), dst=str(new_path))
except Exception as e:
logger.warning(f"Move {str(self.workdir)} to {str(new_path)} error: {e}")
+ finally:
+ if not new_path.exists(): # Recheck for windows os
+ logger.warning(f"Failed to move {str(self.workdir)} to {str(new_path)}")
+ return
logger.info(f"Rename directory {str(self.workdir)} to {str(new_path)}")
self._repository = Repo(new_path)
self._gitignore_rules = parse_gitignore(full_path=str(new_path / ".gitignore"))
diff --git a/metagpt/utils/human_interaction.py b/metagpt/utils/human_interaction.py
new file mode 100644
index 000000000..3b245cac8
--- /dev/null
+++ b/metagpt/utils/human_interaction.py
@@ -0,0 +1,107 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# @Desc : human interaction to get required type text
+
+import json
+from typing import Any, Tuple, Type
+
+from pydantic import BaseModel
+
+from metagpt.logs import logger
+from metagpt.utils.common import import_class
+
+
+class HumanInteraction(object):
+ stop_list = ("q", "quit", "exit")
+
+ def multilines_input(self, prompt: str = "Enter: ") -> str:
+ logger.warning("Enter your content, use Ctrl-D or Ctrl-Z ( windows ) to save it.")
+ logger.info(f"{prompt}\n")
+ lines = []
+ while True:
+ try:
+ line = input()
+ lines.append(line)
+ except EOFError:
+ break
+ return "".join(lines)
+
+ def check_input_type(self, input_str: str, req_type: Type) -> Tuple[bool, Any]:
+ check_ret = True
+ if req_type == str:
+ # required_type = str, just return True
+ return check_ret, input_str
+ try:
+ input_str = input_str.strip()
+ data = json.loads(input_str)
+ except Exception:
+ return False, None
+
+ actionnode_class = import_class("ActionNode", "metagpt.actions.action_node") # avoid circular import
+ tmp_key = "tmp"
+ tmp_cls = actionnode_class.create_model_class(class_name=tmp_key.upper(), mapping={tmp_key: (req_type, ...)})
+ try:
+ _ = tmp_cls(**{tmp_key: data})
+ except Exception:
+ check_ret = False
+ return check_ret, data
+
+ def input_until_valid(self, prompt: str, req_type: Type) -> Any:
+ # check the input with req_type until it's ok
+ while True:
+ input_content = self.multilines_input(prompt)
+ check_ret, structure_content = self.check_input_type(input_content, req_type)
+ if check_ret:
+ break
+ else:
+ logger.error(f"Input content can't meet required_type: {req_type}, please Re-Enter.")
+ return structure_content
+
+ def input_num_until_valid(self, num_max: int) -> int:
+ while True:
+ input_num = input("Enter the num of the interaction key: ")
+ input_num = input_num.strip()
+ if input_num in self.stop_list:
+ return input_num
+ try:
+ input_num = int(input_num)
+ if 0 <= input_num < num_max:
+ return input_num
+ except Exception:
+ pass
+
+ def interact_with_instruct_content(
+ self, instruct_content: BaseModel, mapping: dict = dict(), interact_type: str = "review"
+ ) -> dict[str, Any]:
+ assert interact_type in ["review", "revise"]
+ assert instruct_content
+ instruct_content_dict = instruct_content.model_dump()
+ num_fields_map = dict(zip(range(0, len(instruct_content_dict)), instruct_content_dict.keys()))
+ logger.info(
+ f"\n{interact_type.upper()} interaction\n"
+ f"Interaction data: {num_fields_map}\n"
+ f"Enter the num to interact with corresponding field or `q`/`quit`/`exit` to stop interaction.\n"
+ f"Enter the field content until it meet field required type.\n"
+ )
+
+ interact_contents = {}
+ while True:
+ input_num = self.input_num_until_valid(len(instruct_content_dict))
+ if input_num in self.stop_list:
+ logger.warning("Stop human interaction")
+ break
+
+ field = num_fields_map.get(input_num)
+ logger.info(f"You choose to interact with field: {field}, and do a `{interact_type}` operation.")
+
+ if interact_type == "review":
+ prompt = "Enter your review comment: "
+ req_type = str
+ else:
+ prompt = "Enter your revise content: "
+ req_type = mapping.get(field)[0] # revise need input content match the required_type
+
+ field_content = self.input_until_valid(prompt=prompt, req_type=req_type)
+ interact_contents[field] = field_content
+
+ return interact_contents
diff --git a/metagpt/utils/make_sk_kernel.py b/metagpt/utils/make_sk_kernel.py
index e0272ea13..283a682d6 100644
--- a/metagpt/utils/make_sk_kernel.py
+++ b/metagpt/utils/make_sk_kernel.py
@@ -13,20 +13,20 @@ from semantic_kernel.connectors.ai.open_ai.services.open_ai_chat_completion impo
OpenAIChatCompletion,
)
-from metagpt.config import CONFIG
+from metagpt.config2 import config
def make_sk_kernel():
kernel = sk.Kernel()
- if CONFIG.OPENAI_API_TYPE == "azure":
+ if llm := config.get_azure_llm():
kernel.add_chat_service(
"chat_completion",
- AzureChatCompletion(CONFIG.DEPLOYMENT_NAME, CONFIG.OPENAI_BASE_URL, CONFIG.OPENAI_API_KEY),
+ AzureChatCompletion(llm.model, llm.base_url, llm.api_key),
)
- else:
+ elif llm := config.get_openai_llm():
kernel.add_chat_service(
"chat_completion",
- OpenAIChatCompletion(CONFIG.OPENAI_API_MODEL, CONFIG.OPENAI_API_KEY),
+ OpenAIChatCompletion(llm.model, llm.api_key),
)
return kernel
diff --git a/metagpt/utils/mermaid.py b/metagpt/utils/mermaid.py
index 235b4979c..e49fdea5d 100644
--- a/metagpt/utils/mermaid.py
+++ b/metagpt/utils/mermaid.py
@@ -4,7 +4,6 @@
@Time : 2023/7/4 10:53
@Author : alexanderwu alitrack
@File : mermaid.py
-@Modified By: mashenquan, 2023/8/20. Remove global configuration `CONFIG`, enable configuration support for business isolation.
"""
import asyncio
import os
@@ -12,12 +11,12 @@ from pathlib import Path
import aiofiles
-from metagpt.config import CONFIG
+from metagpt.config2 import config
from metagpt.logs import logger
from metagpt.utils.common import check_cmd_exists
-async def mermaid_to_file(mermaid_code, output_file_without_suffix, width=2048, height=2048) -> int:
+async def mermaid_to_file(engine, mermaid_code, output_file_without_suffix, width=2048, height=2048) -> int:
"""suffix: png/svg/pdf
:param mermaid_code: mermaid code
@@ -35,9 +34,8 @@ async def mermaid_to_file(mermaid_code, output_file_without_suffix, width=2048,
await f.write(mermaid_code)
# tmp.write_text(mermaid_code, encoding="utf-8")
- engine = CONFIG.mermaid_engine.lower()
if engine == "nodejs":
- if check_cmd_exists(CONFIG.mmdc) != 0:
+ if check_cmd_exists(config.mmdc) != 0:
logger.warning(
"RUN `npm install -g @mermaid-js/mermaid-cli` to install mmdc,"
"or consider changing MERMAID_ENGINE to `playwright`, `pyppeteer`, or `ink`."
@@ -49,11 +47,11 @@ async def mermaid_to_file(mermaid_code, output_file_without_suffix, width=2048,
# Call the `mmdc` command to convert the Mermaid code to a PNG
logger.info(f"Generating {output_file}..")
- if CONFIG.puppeteer_config:
+ if config.puppeteer_config:
commands = [
- CONFIG.mmdc,
+ config.mmdc,
"-p",
- CONFIG.puppeteer_config,
+ config.puppeteer_config,
"-i",
str(tmp),
"-o",
@@ -64,7 +62,7 @@ async def mermaid_to_file(mermaid_code, output_file_without_suffix, width=2048,
str(height),
]
else:
- commands = [CONFIG.mmdc, "-i", str(tmp), "-o", output_file, "-w", str(width), "-H", str(height)]
+ commands = [config.mmdc, "-i", str(tmp), "-o", output_file, "-w", str(width), "-H", str(height)]
process = await asyncio.create_subprocess_shell(
" ".join(commands), stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE
)
diff --git a/metagpt/utils/mmdc_pyppeteer.py b/metagpt/utils/mmdc_pyppeteer.py
index 7125cafc5..d80098b7d 100644
--- a/metagpt/utils/mmdc_pyppeteer.py
+++ b/metagpt/utils/mmdc_pyppeteer.py
@@ -10,7 +10,7 @@ from urllib.parse import urljoin
from pyppeteer import launch
-from metagpt.config import CONFIG
+from metagpt.config2 import config
from metagpt.logs import logger
@@ -30,10 +30,10 @@ async def mermaid_to_file(mermaid_code, output_file_without_suffix, width=2048,
suffixes = ["png", "svg", "pdf"]
__dirname = os.path.dirname(os.path.abspath(__file__))
- if CONFIG.pyppeteer_executable_path:
+ if config.pyppeteer_executable_path:
browser = await launch(
headless=True,
- executablePath=CONFIG.pyppeteer_executable_path,
+ executablePath=config.pyppeteer_executable_path,
args=["--disable-extensions", "--no-sandbox"],
)
else:
diff --git a/metagpt/utils/project_repo.py b/metagpt/utils/project_repo.py
new file mode 100644
index 000000000..72bca7ea0
--- /dev/null
+++ b/metagpt/utils/project_repo.py
@@ -0,0 +1,139 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+"""
+@Time : 2024/1/8
+@Author : mashenquan
+@File : project_repo.py
+@Desc : Wrapper for GitRepository and FileRepository of project.
+ Implementation of Chapter 4.6 of https://deepwisdom.feishu.cn/wiki/CUK4wImd7id9WlkQBNscIe9cnqh
+"""
+from __future__ import annotations
+
+from pathlib import Path
+
+from metagpt.const import (
+ CLASS_VIEW_FILE_REPO,
+ CODE_PLAN_AND_CHANGE_FILE_REPO,
+ CODE_PLAN_AND_CHANGE_PDF_FILE_REPO,
+ CODE_SUMMARIES_FILE_REPO,
+ CODE_SUMMARIES_PDF_FILE_REPO,
+ COMPETITIVE_ANALYSIS_FILE_REPO,
+ DATA_API_DESIGN_FILE_REPO,
+ DOCS_FILE_REPO,
+ GRAPH_REPO_FILE_REPO,
+ PRD_PDF_FILE_REPO,
+ PRDS_FILE_REPO,
+ REQUIREMENT_FILENAME,
+ RESOURCES_FILE_REPO,
+ SD_OUTPUT_FILE_REPO,
+ SEQ_FLOW_FILE_REPO,
+ SYSTEM_DESIGN_FILE_REPO,
+ SYSTEM_DESIGN_PDF_FILE_REPO,
+ TASK_FILE_REPO,
+ TASK_PDF_FILE_REPO,
+ TEST_CODES_FILE_REPO,
+ TEST_OUTPUTS_FILE_REPO,
+)
+from metagpt.utils.file_repository import FileRepository
+from metagpt.utils.git_repository import GitRepository
+
+
+class DocFileRepositories(FileRepository):
+ prd: FileRepository
+ system_design: FileRepository
+ task: FileRepository
+ code_summary: FileRepository
+ graph_repo: FileRepository
+ class_view: FileRepository
+ code_plan_and_change: FileRepository
+
+ def __init__(self, git_repo):
+ super().__init__(git_repo=git_repo, relative_path=DOCS_FILE_REPO)
+
+ self.prd = git_repo.new_file_repository(relative_path=PRDS_FILE_REPO)
+ self.system_design = git_repo.new_file_repository(relative_path=SYSTEM_DESIGN_FILE_REPO)
+ self.task = git_repo.new_file_repository(relative_path=TASK_FILE_REPO)
+ self.code_summary = git_repo.new_file_repository(relative_path=CODE_SUMMARIES_FILE_REPO)
+ self.graph_repo = git_repo.new_file_repository(relative_path=GRAPH_REPO_FILE_REPO)
+ self.class_view = git_repo.new_file_repository(relative_path=CLASS_VIEW_FILE_REPO)
+ self.code_plan_and_change = git_repo.new_file_repository(relative_path=CODE_PLAN_AND_CHANGE_FILE_REPO)
+
+
+class ResourceFileRepositories(FileRepository):
+ competitive_analysis: FileRepository
+ data_api_design: FileRepository
+ seq_flow: FileRepository
+ system_design: FileRepository
+ prd: FileRepository
+ api_spec_and_task: FileRepository
+ code_summary: FileRepository
+ sd_output: FileRepository
+ code_plan_and_change: FileRepository
+
+ def __init__(self, git_repo):
+ super().__init__(git_repo=git_repo, relative_path=RESOURCES_FILE_REPO)
+
+ self.competitive_analysis = git_repo.new_file_repository(relative_path=COMPETITIVE_ANALYSIS_FILE_REPO)
+ self.data_api_design = git_repo.new_file_repository(relative_path=DATA_API_DESIGN_FILE_REPO)
+ self.seq_flow = git_repo.new_file_repository(relative_path=SEQ_FLOW_FILE_REPO)
+ self.system_design = git_repo.new_file_repository(relative_path=SYSTEM_DESIGN_PDF_FILE_REPO)
+ self.prd = git_repo.new_file_repository(relative_path=PRD_PDF_FILE_REPO)
+ self.api_spec_and_task = git_repo.new_file_repository(relative_path=TASK_PDF_FILE_REPO)
+ self.code_summary = git_repo.new_file_repository(relative_path=CODE_SUMMARIES_PDF_FILE_REPO)
+ self.sd_output = git_repo.new_file_repository(relative_path=SD_OUTPUT_FILE_REPO)
+ self.code_plan_and_change = git_repo.new_file_repository(relative_path=CODE_PLAN_AND_CHANGE_PDF_FILE_REPO)
+
+
+class ProjectRepo(FileRepository):
+ def __init__(self, root: str | Path | GitRepository):
+ if isinstance(root, str) or isinstance(root, Path):
+ git_repo_ = GitRepository(local_path=Path(root))
+ elif isinstance(root, GitRepository):
+ git_repo_ = root
+ else:
+ raise ValueError("Invalid root")
+ super().__init__(git_repo=git_repo_, relative_path=Path("."))
+ self._git_repo = git_repo_
+ self.docs = DocFileRepositories(self._git_repo)
+ self.resources = ResourceFileRepositories(self._git_repo)
+ self.tests = self._git_repo.new_file_repository(relative_path=TEST_CODES_FILE_REPO)
+ self.test_outputs = self._git_repo.new_file_repository(relative_path=TEST_OUTPUTS_FILE_REPO)
+ self._srcs_path = None
+
+ @property
+ async def requirement(self):
+ return await self.docs.get(filename=REQUIREMENT_FILENAME)
+
+ @property
+ def git_repo(self) -> GitRepository:
+ return self._git_repo
+
+ @property
+ def workdir(self) -> Path:
+ return Path(self.git_repo.workdir)
+
+ @property
+ def srcs(self) -> FileRepository:
+ if not self._srcs_path:
+ raise ValueError("Call with_srcs first.")
+ return self._git_repo.new_file_repository(self._srcs_path)
+
+ def code_files_exists(self) -> bool:
+ git_workdir = self.git_repo.workdir
+ src_workdir = git_workdir / git_workdir.name
+ if not src_workdir.exists():
+ return False
+ code_files = self.with_src_path(path=git_workdir / git_workdir.name).srcs.all_files
+ if not code_files:
+ return False
+
+ def with_src_path(self, path: str | Path) -> ProjectRepo:
+ try:
+ self._srcs_path = Path(path).relative_to(self.workdir)
+ except ValueError:
+ self._srcs_path = Path(path)
+ return self
+
+ @property
+ def src_relative_path(self) -> Path | None:
+ return self._srcs_path
diff --git a/metagpt/utils/redis.py b/metagpt/utils/redis.py
index 10f33285c..7a640563a 100644
--- a/metagpt/utils/redis.py
+++ b/metagpt/utils/redis.py
@@ -12,26 +12,25 @@ from datetime import timedelta
import aioredis # https://aioredis.readthedocs.io/en/latest/getting-started/
-from metagpt.config import CONFIG
+from metagpt.configs.redis_config import RedisConfig
from metagpt.logs import logger
class Redis:
- def __init__(self):
+ def __init__(self, config: RedisConfig = None):
+ self.config = config
self._client = None
async def _connect(self, force=False):
if self._client and not force:
return True
- if not self.is_configured:
- return False
try:
self._client = await aioredis.from_url(
- f"redis://{CONFIG.REDIS_HOST}:{CONFIG.REDIS_PORT}",
- username=CONFIG.REDIS_USER,
- password=CONFIG.REDIS_PASSWORD,
- db=CONFIG.REDIS_DB,
+ self.config.to_url(),
+ username=self.config.username,
+ password=self.config.password,
+ db=self.config.db,
)
return True
except Exception as e:
@@ -62,18 +61,3 @@ class Redis:
return
await self._client.close()
self._client = None
-
- @property
- def is_valid(self) -> bool:
- return self._client is not None
-
- @property
- def is_configured(self) -> bool:
- return bool(
- CONFIG.REDIS_HOST
- and CONFIG.REDIS_HOST != "YOUR_REDIS_HOST"
- and CONFIG.REDIS_PORT
- and CONFIG.REDIS_PORT != "YOUR_REDIS_PORT"
- and CONFIG.REDIS_DB is not None
- and CONFIG.REDIS_PASSWORD is not None
- )
diff --git a/metagpt/utils/repair_llm_raw_output.py b/metagpt/utils/repair_llm_raw_output.py
index a96c3dce0..82b2dd5b1 100644
--- a/metagpt/utils/repair_llm_raw_output.py
+++ b/metagpt/utils/repair_llm_raw_output.py
@@ -9,7 +9,7 @@ from typing import Callable, Union
import regex as re
from tenacity import RetryCallState, retry, stop_after_attempt, wait_fixed
-from metagpt.config import CONFIG
+from metagpt.config2 import config
from metagpt.logs import logger
from metagpt.utils.custom_decoder import CustomDecoder
@@ -120,6 +120,15 @@ def repair_json_format(output: str) -> str:
elif output.startswith("{") and output.endswith("]"):
output = output[:-1] + "}"
+ # remove `#` in output json str, usually appeared in `glm-4`
+ arr = output.split("\n")
+ new_arr = []
+ for line in arr:
+ idx = line.find("#")
+ if idx >= 0:
+ line = line[:idx]
+ new_arr.append(line)
+ output = "\n".join(new_arr)
return output
@@ -152,7 +161,7 @@ def repair_llm_raw_output(output: str, req_keys: list[str], repair_type: RepairT
target: { xxx }
output: { xxx }]
"""
- if not CONFIG.repair_llm_output:
+ if not config.repair_llm_output:
return output
# do the repairation usually for non-openai models
@@ -168,15 +177,17 @@ def repair_invalid_json(output: str, error: str) -> str:
example 1. json.decoder.JSONDecodeError: Expecting ',' delimiter: line 154 column 1 (char 2765)
example 2. xxx.JSONDecodeError: Expecting property name enclosed in double quotes: line 14 column 1 (char 266)
"""
- pattern = r"line ([0-9]+)"
+ pattern = r"line ([0-9]+) column ([0-9]+)"
matches = re.findall(pattern, error, re.DOTALL)
if len(matches) > 0:
- line_no = int(matches[0]) - 1
+ line_no = int(matches[0][0]) - 1
+ col_no = int(matches[0][1]) - 1
# due to CustomDecoder can handle `"": ''` or `'': ""`, so convert `"""` -> `"`, `'''` -> `'`
output = output.replace('"""', '"').replace("'''", '"')
arr = output.split("\n")
+ rline = arr[line_no] # raw line
line = arr[line_no].strip()
# different general problems
if line.endswith("],"):
@@ -187,9 +198,12 @@ def repair_invalid_json(output: str, error: str) -> str:
new_line = line.replace("}", "")
elif line.endswith("},") and output.endswith("},"):
new_line = line[:-1]
- elif '",' not in line and "," not in line:
+ elif (rline[col_no] in ["'", '"']) and (line.startswith('"') or line.startswith("'")) and "," not in line:
+ # problem, `"""` or `'''` without `,`
+ new_line = f",{line}"
+ elif '",' not in line and "," not in line and '"' not in line:
new_line = f'{line}",'
- elif "," not in line:
+ elif not line.endswith(","):
# problem, miss char `,` at the end.
new_line = f"{line},"
elif "," in line and len(line) == 1:
@@ -231,7 +245,7 @@ def run_after_exp_and_passon_next_retry(logger: "loguru.Logger") -> Callable[["R
func_param_output = retry_state.kwargs.get("output", "")
exp_str = str(retry_state.outcome.exception())
- fix_str = "try to fix it, " if CONFIG.repair_llm_output else ""
+ fix_str = "try to fix it, " if config.repair_llm_output else ""
logger.warning(
f"parse json from content inside [CONTENT][/CONTENT] failed at retry "
f"{retry_state.attempt_number}, {fix_str}exp: {exp_str}"
@@ -244,7 +258,7 @@ def run_after_exp_and_passon_next_retry(logger: "loguru.Logger") -> Callable[["R
@retry(
- stop=stop_after_attempt(3 if CONFIG.repair_llm_output else 0),
+ stop=stop_after_attempt(3 if config.repair_llm_output else 0),
wait=wait_fixed(1),
after=run_after_exp_and_passon_next_retry(logger),
)
diff --git a/metagpt/utils/s3.py b/metagpt/utils/s3.py
index 2a2c1a31c..c0afbb2f5 100644
--- a/metagpt/utils/s3.py
+++ b/metagpt/utils/s3.py
@@ -8,7 +8,7 @@ from typing import Optional
import aioboto3
import aiofiles
-from metagpt.config import CONFIG
+from metagpt.config2 import S3Config
from metagpt.const import BASE64_FORMAT
from metagpt.logs import logger
@@ -16,13 +16,14 @@ from metagpt.logs import logger
class S3:
"""A class for interacting with Amazon S3 storage."""
- def __init__(self):
+ def __init__(self, config: S3Config):
self.session = aioboto3.Session()
+ self.config = config
self.auth_config = {
"service_name": "s3",
- "aws_access_key_id": CONFIG.S3_ACCESS_KEY,
- "aws_secret_access_key": CONFIG.S3_SECRET_KEY,
- "endpoint_url": CONFIG.S3_ENDPOINT_URL,
+ "aws_access_key_id": config.access_key,
+ "aws_secret_access_key": config.secret_key,
+ "endpoint_url": config.endpoint,
}
async def upload_file(
@@ -139,8 +140,8 @@ class S3:
data = base64.b64decode(data) if format == BASE64_FORMAT else data.encode(encoding="utf-8")
await file.write(data)
- bucket = CONFIG.S3_BUCKET
- object_pathname = CONFIG.S3_BUCKET or "system"
+ bucket = self.config.bucket
+ object_pathname = self.config.bucket or "system"
object_pathname += f"/{object_name}"
object_pathname = os.path.normpath(object_pathname)
await self.upload_file(bucket=bucket, local_path=str(pathname), object_name=object_pathname)
@@ -151,20 +152,3 @@ class S3:
logger.exception(f"{e}, stack:{traceback.format_exc()}")
pathname.unlink(missing_ok=True)
return None
-
- @property
- def is_valid(self):
- return self.is_configured
-
- @property
- def is_configured(self) -> bool:
- return bool(
- CONFIG.S3_ACCESS_KEY
- and CONFIG.S3_ACCESS_KEY != "YOUR_S3_ACCESS_KEY"
- and CONFIG.S3_SECRET_KEY
- and CONFIG.S3_SECRET_KEY != "YOUR_S3_SECRET_KEY"
- and CONFIG.S3_ENDPOINT_URL
- and CONFIG.S3_ENDPOINT_URL != "YOUR_S3_ENDPOINT_URL"
- and CONFIG.S3_BUCKET
- and CONFIG.S3_BUCKET != "YOUR_S3_BUCKET"
- )
diff --git a/metagpt/utils/token_counter.py b/metagpt/utils/token_counter.py
index a1b74a074..94506e373 100644
--- a/metagpt/utils/token_counter.py
+++ b/metagpt/utils/token_counter.py
@@ -4,10 +4,11 @@
@Time : 2023/5/18 00:40
@Author : alexanderwu
@File : token_counter.py
-ref1: https://github.com/openai/openai-cookbook/blob/main/examples/How_to_count_tokens_with_tiktoken.ipynb
-ref2: https://github.com/Significant-Gravitas/Auto-GPT/blob/master/autogpt/llm/token_counter.py
-ref3: https://github.com/hwchase17/langchain/blob/master/langchain/chat_models/openai.py
-ref4: https://ai.google.dev/models/gemini
+ref1: https://openai.com/pricing
+ref2: https://github.com/openai/openai-cookbook/blob/main/examples/How_to_count_tokens_with_tiktoken.ipynb
+ref3: https://github.com/Significant-Gravitas/Auto-GPT/blob/master/autogpt/llm/token_counter.py
+ref4: https://github.com/hwchase17/langchain/blob/master/langchain/chat_models/openai.py
+ref5: https://ai.google.dev/models/gemini
"""
import tiktoken
@@ -25,9 +26,13 @@ TOKEN_COSTS = {
"gpt-4-32k": {"prompt": 0.06, "completion": 0.12},
"gpt-4-32k-0314": {"prompt": 0.06, "completion": 0.12},
"gpt-4-0613": {"prompt": 0.06, "completion": 0.12},
+ "gpt-4-turbo-preview": {"prompt": 0.01, "completion": 0.03},
+ "gpt-4-0125-preview": {"prompt": 0.01, "completion": 0.03},
"gpt-4-1106-preview": {"prompt": 0.01, "completion": 0.03},
+ "gpt-4-1106-vision-preview": {"prompt": 0.01, "completion": 0.03},
"text-embedding-ada-002": {"prompt": 0.0004, "completion": 0.0},
- "chatglm_turbo": {"prompt": 0.0, "completion": 0.00069}, # 32k version, prompt + completion tokens=0.005¥/k-tokens
+ "glm-3-turbo": {"prompt": 0.0, "completion": 0.0007}, # 128k version, prompt + completion tokens=0.005¥/k-tokens
+ "glm-4": {"prompt": 0.0, "completion": 0.014}, # 128k version, prompt + completion tokens=0.1¥/k-tokens
"gemini-pro": {"prompt": 0.00025, "completion": 0.0005},
}
@@ -46,7 +51,10 @@ TOKEN_MAX = {
"gpt-4-32k": 32768,
"gpt-4-32k-0314": 32768,
"gpt-4-0613": 8192,
+ "gpt-4-turbo-preview": 128000,
+ "gpt-4-0125-preview": 128000,
"gpt-4-1106-preview": 128000,
+ "gpt-4-1106-vision-preview": 128000,
"text-embedding-ada-002": 8192,
"chatglm_turbo": 32768,
"gemini-pro": 32768,
@@ -71,7 +79,10 @@ def count_message_tokens(messages, model="gpt-3.5-turbo-0613"):
"gpt-4-32k-0314",
"gpt-4-0613",
"gpt-4-32k-0613",
+ "gpt-4-turbo-preview",
+ "gpt-4-0125-preview",
"gpt-4-1106-preview",
+ "gpt-4-1106-vision-preview",
}:
tokens_per_message = 3 # # every reply is primed with <|start|>assistant<|message|>
tokens_per_name = 1
diff --git a/metagpt/utils/yaml_model.py b/metagpt/utils/yaml_model.py
new file mode 100644
index 000000000..8f2d22c3d
--- /dev/null
+++ b/metagpt/utils/yaml_model.py
@@ -0,0 +1,48 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+"""
+@Time : 2024/1/4 10:18
+@Author : alexanderwu
+@File : YamlModel.py
+"""
+from pathlib import Path
+from typing import Dict, Optional
+
+import yaml
+from pydantic import BaseModel, model_validator
+
+
+class YamlModel(BaseModel):
+ """Base class for yaml model"""
+
+ extra_fields: Optional[Dict[str, str]] = None
+
+ @classmethod
+ def read_yaml(cls, file_path: Path, encoding: str = "utf-8") -> Dict:
+ """Read yaml file and return a dict"""
+ if not file_path.exists():
+ return {}
+ with open(file_path, "r", encoding=encoding) as file:
+ return yaml.safe_load(file)
+
+ @classmethod
+ def from_yaml_file(cls, file_path: Path) -> "YamlModel":
+ """Read yaml file and return a YamlModel instance"""
+ return cls(**cls.read_yaml(file_path))
+
+ def to_yaml_file(self, file_path: Path, encoding: str = "utf-8") -> None:
+ """Dump YamlModel instance to yaml file"""
+ with open(file_path, "w", encoding=encoding) as file:
+ yaml.dump(self.model_dump(), file)
+
+
+class YamlModelWithoutDefault(YamlModel):
+ """YamlModel without default values"""
+
+ @model_validator(mode="before")
+ @classmethod
+ def check_not_default_config(cls, values):
+ """Check if there is any default config in config.yaml"""
+ if any(["YOUR" in v for v in values]):
+ raise ValueError("Please set your config in config.yaml")
+ return values
diff --git a/requirements.txt b/requirements.txt
index 016c2f5d5..66b3c9fc0 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -14,7 +14,7 @@ lancedb==0.4.0
langchain==0.0.352
loguru==0.6.0
meilisearch==0.21.0
-numpy==1.24.3
+numpy>=1.24.3
openai==1.6.0
openpyxl
beautifulsoup4==4.12.2
@@ -49,7 +49,7 @@ aioredis~=2.0.1 # Used by metagpt/utils/redis.py
websocket-client==1.6.2
aiofiles==23.2.1
gitpython==3.1.40
-zhipuai==1.0.7
+zhipuai==2.0.1
rich==13.6.0
nbclient==0.9.0
nbformat==5.9.2
diff --git a/setup.py b/setup.py
index 0439d6cd4..d1445e3f8 100644
--- a/setup.py
+++ b/setup.py
@@ -46,8 +46,8 @@ extras_require["test"] = [
"chromadb==0.4.14",
"gradio==3.0.0",
"grpcio-status==1.48.2",
- "mock==5.1.0",
"pylint==3.0.3",
+ "pybrowsers",
]
extras_require["pyppeteer"] = [
@@ -58,7 +58,7 @@ extras_require["dev"] = (["pylint~=3.0.3", "black~=23.3.0", "isort~=5.12.0", "pr
setup(
name="metagpt",
- version="0.6.0",
+ version="0.7.0",
description="The Multi-Agent Framework",
long_description=long_description,
long_description_content_type="text/markdown",
diff --git a/tests/conftest.py b/tests/conftest.py
index 7dec506bb..c69fca15e 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -12,14 +12,19 @@ import logging
import os
import re
import uuid
+from typing import Callable
import pytest
-from metagpt.config import CONFIG, Config
from metagpt.const import DEFAULT_WORKSPACE_ROOT, TEST_DATA_PATH
+from metagpt.context import Context as MetagptContext
from metagpt.llm import LLM
from metagpt.logs import logger
from metagpt.utils.git_repository import GitRepository
+from metagpt.utils.project_repo import ProjectRepo
+from tests.mock.mock_aiohttp import MockAioResponse
+from tests.mock.mock_curl_cffi import MockCurlCffiResponse
+from tests.mock.mock_httplib2 import MockHttplib2Response
from tests.mock.mock_llm import MockLLM
RSP_CACHE_NEW = {} # used globally for producing new and useful only response cache
@@ -30,7 +35,6 @@ ALLOW_OPENAI_API_CALL = int(
@pytest.fixture(scope="session")
def rsp_cache():
- # model_version = CONFIG.openai_api_model
rsp_cache_file_path = TEST_DATA_PATH / "rsp_cache.json" # read repo-provided
new_rsp_cache_file_path = TEST_DATA_PATH / "rsp_cache_new.json" # exporting a new copy
if os.path.exists(rsp_cache_file_path):
@@ -76,7 +80,7 @@ def llm_mock(rsp_cache, mocker, request):
class Context:
def __init__(self):
self._llm_ui = None
- self._llm_api = LLM(provider=CONFIG.get_default_llm_provider_enum())
+ self._llm_api = LLM()
@property
def llm_api(self):
@@ -90,9 +94,9 @@ class Context:
@pytest.fixture(scope="package")
def llm_api():
logger.info("Setting up the test")
- _context = Context()
+ g_context = Context()
- yield _context.llm_api
+ yield g_context.llm_api
logger.info("Tearing down the test")
@@ -125,7 +129,7 @@ def proxy():
server = await asyncio.start_server(handle_client, "127.0.0.1", 0)
return server, "http://{}:{}".format(*server.sockets[0].getsockname())
- return proxy_func()
+ return proxy_func
# see https://github.com/Delgan/loguru/issues/59#issuecomment-466591978
@@ -139,23 +143,25 @@ def loguru_caplog(caplog):
yield caplog
-# init & dispose git repo
-@pytest.fixture(scope="function", autouse=True)
-def setup_and_teardown_git_repo(request):
- CONFIG.git_repo = GitRepository(local_path=DEFAULT_WORKSPACE_ROOT / f"unittest/{uuid.uuid4().hex}")
- CONFIG.git_reinit = True
+@pytest.fixture(scope="function")
+def context(request):
+ ctx = MetagptContext()
+ ctx.git_repo = GitRepository(local_path=DEFAULT_WORKSPACE_ROOT / f"unittest/{uuid.uuid4().hex}")
+ ctx.repo = ProjectRepo(ctx.git_repo)
# Destroy git repo at the end of the test session.
def fin():
- CONFIG.git_repo.delete_repository()
+ if ctx.git_repo:
+ ctx.git_repo.delete_repository()
# Register the function for destroying the environment.
request.addfinalizer(fin)
+ return ctx
@pytest.fixture(scope="session", autouse=True)
def init_config():
- Config()
+ pass
@pytest.fixture(scope="function")
@@ -165,39 +171,63 @@ def new_filename(mocker):
yield mocker
+@pytest.fixture(scope="session")
+def search_rsp_cache():
+ rsp_cache_file_path = TEST_DATA_PATH / "search_rsp_cache.json" # read repo-provided
+ if os.path.exists(rsp_cache_file_path):
+ with open(rsp_cache_file_path, "r") as f1:
+ rsp_cache_json = json.load(f1)
+ else:
+ rsp_cache_json = {}
+ yield rsp_cache_json
+ with open(rsp_cache_file_path, "w") as f2:
+ json.dump(rsp_cache_json, f2, indent=4, ensure_ascii=False)
+
+
@pytest.fixture
def aiohttp_mocker(mocker):
- class MockAioResponse:
- async def json(self, *args, **kwargs):
- return self._json
-
- def set_json(self, json):
- self._json = json
-
- response = MockAioResponse()
-
- class MockCTXMng:
- async def __aenter__(self):
- return response
-
- async def __aexit__(self, *args, **kwargs):
- pass
-
- def __await__(self):
- yield
- return response
-
- def mock_request(self, method, url, **kwargs):
- return MockCTXMng()
+ MockResponse = type("MockResponse", (MockAioResponse,), {})
def wrap(method):
def run(self, url, **kwargs):
- return mock_request(self, method, url, **kwargs)
+ return MockResponse(self, method, url, **kwargs)
return run
- mocker.patch("aiohttp.ClientSession.request", mock_request)
+ mocker.patch("aiohttp.ClientSession.request", MockResponse)
for i in ["get", "post", "delete", "patch"]:
mocker.patch(f"aiohttp.ClientSession.{i}", wrap(i))
+ yield MockResponse
- yield response
+
+@pytest.fixture
+def curl_cffi_mocker(mocker):
+ MockResponse = type("MockResponse", (MockCurlCffiResponse,), {})
+
+ def request(self, *args, **kwargs):
+ return MockResponse(self, *args, **kwargs)
+
+ mocker.patch("curl_cffi.requests.Session.request", request)
+ yield MockResponse
+
+
+@pytest.fixture
+def httplib2_mocker(mocker):
+ MockResponse = type("MockResponse", (MockHttplib2Response,), {})
+
+ def request(self, *args, **kwargs):
+ return MockResponse(self, *args, **kwargs)
+
+ mocker.patch("httplib2.Http.request", request)
+ yield MockResponse
+
+
+@pytest.fixture
+def search_engine_mocker(aiohttp_mocker, curl_cffi_mocker, httplib2_mocker, search_rsp_cache):
+ # aiohttp_mocker: serpapi/serper
+ # httplib2_mocker: google
+ # curl_cffi_mocker: ddg
+ check_funcs: dict[tuple[str, str], Callable[[dict], str]] = {}
+ aiohttp_mocker.rsp_cache = httplib2_mocker.rsp_cache = curl_cffi_mocker.rsp_cache = search_rsp_cache
+ aiohttp_mocker.check_funcs = httplib2_mocker.check_funcs = curl_cffi_mocker.check_funcs = check_funcs
+ yield check_funcs
diff --git a/tests/data/audio/hello.mp3 b/tests/data/audio/hello.mp3
new file mode 100644
index 000000000..7b3aab0a4
Binary files /dev/null and b/tests/data/audio/hello.mp3 differ
diff --git a/tests/data/demo_project/dependencies.json b/tests/data/demo_project/dependencies.json
index cfcf6c165..738e5d9be 100644
--- a/tests/data/demo_project/dependencies.json
+++ b/tests/data/demo_project/dependencies.json
@@ -1 +1 @@
-{"docs/system_design/20231221155954.json": ["docs/prds/20231221155954.json"], "docs/tasks/20231221155954.json": ["docs/system_design/20231221155954.json"], "game_2048/game.py": ["docs/tasks/20231221155954.json", "docs/system_design/20231221155954.json"], "game_2048/main.py": ["docs/tasks/20231221155954.json", "docs/system_design/20231221155954.json"], "resources/code_summaries/20231221155954.md": ["docs/tasks/20231221155954.json", "game_2048/game.py", "docs/system_design/20231221155954.json", "game_2048/main.py"], "docs/code_summaries/20231221155954.json": ["docs/tasks/20231221155954.json", "game_2048/game.py", "docs/system_design/20231221155954.json", "game_2048/main.py"], "tests/test_main.py": ["game_2048/main.py"], "tests/test_game.py": ["game_2048/game.py"], "test_outputs/test_main.py.json": ["game_2048/main.py", "tests/test_main.py"], "test_outputs/test_game.py.json": ["game_2048/game.py", "tests/test_game.py"]}
\ No newline at end of file
+{"docs/system_design/20231221155954.json": ["docs/prd/20231221155954.json"], "docs/task/20231221155954.json": ["docs/system_design/20231221155954.json"], "game_2048/game.py": ["docs/task/20231221155954.json", "docs/system_design/20231221155954.json"], "game_2048/main.py": ["docs/task/20231221155954.json", "docs/system_design/20231221155954.json"], "resources/code_summary/20231221155954.md": ["docs/task/20231221155954.json", "game_2048/game.py", "docs/system_design/20231221155954.json", "game_2048/main.py"], "docs/code_summary/20231221155954.json": ["docs/task/20231221155954.json", "game_2048/game.py", "docs/system_design/20231221155954.json", "game_2048/main.py"], "tests/test_main.py": ["game_2048/main.py"], "tests/test_game.py": ["game_2048/game.py"], "test_outputs/test_main.py.json": ["game_2048/main.py", "tests/test_main.py"], "test_outputs/test_game.py.json": ["game_2048/game.py", "tests/test_game.py"]}
\ No newline at end of file
diff --git a/tests/data/incremental_dev_project/Gomoku.zip b/tests/data/incremental_dev_project/Gomoku.zip
new file mode 100644
index 000000000..23649565a
Binary files /dev/null and b/tests/data/incremental_dev_project/Gomoku.zip differ
diff --git a/tests/data/incremental_dev_project/dice_simulator_new.zip b/tests/data/incremental_dev_project/dice_simulator_new.zip
new file mode 100644
index 000000000..4752ab4c5
Binary files /dev/null and b/tests/data/incremental_dev_project/dice_simulator_new.zip differ
diff --git a/tests/data/incremental_dev_project/mock.py b/tests/data/incremental_dev_project/mock.py
new file mode 100644
index 000000000..f2eb71359
--- /dev/null
+++ b/tests/data/incremental_dev_project/mock.py
@@ -0,0 +1,466 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+"""
+@Time : 2024/01/17
+@Author : mannaandpoem
+@File : mock.py
+"""
+NEW_REQUIREMENT_SAMPLE = """
+Adding graphical interface functionality to enhance the user experience in the number-guessing game. The existing number-guessing game currently relies on command-line input for numbers. The goal is to introduce a graphical interface to improve the game's usability and visual appeal
+"""
+
+PRD_SAMPLE = """
+## Language
+
+en_us
+
+## Programming Language
+
+Python
+
+## Original Requirements
+
+Make a simple number guessing game
+
+## Product Goals
+
+- Ensure a user-friendly interface for the game
+- Provide a challenging yet enjoyable game experience
+- Design the game to be easily extendable for future features
+
+## User Stories
+
+- As a player, I want to guess numbers and receive feedback on whether my guess is too high or too low
+- As a player, I want to be able to set the difficulty level by choosing the range of possible numbers
+- As a player, I want to see my previous guesses to strategize my next guess
+- As a player, I want to know how many attempts it took me to guess the number once I get it right
+
+## Competitive Analysis
+
+- Guess The Number Game A: Basic text interface, no difficulty levels
+- Number Master B: Has difficulty levels, but cluttered interface
+- Quick Guess C: Sleek design, but lacks performance tracking
+- NumGuess D: Good performance tracking, but not mobile-friendly
+- GuessIt E: Mobile-friendly, but too many ads
+- Perfect Guess F: Offers hints, but the hints are not very helpful
+- SmartGuesser G: Has a learning mode, but lacks a competitive edge
+
+## Competitive Quadrant Chart
+
+quadrantChart
+ title "User Engagement and Game Complexity"
+ x-axis "Low Complexity" --> "High Complexity"
+ y-axis "Low Engagement" --> "High Engagement"
+ quadrant-1 "Too Simple"
+ quadrant-2 "Niche Appeal"
+ quadrant-3 "Complex & Unengaging"
+ quadrant-4 "Sweet Spot"
+ "Guess The Number Game A": [0.2, 0.4]
+ "Number Master B": [0.5, 0.3]
+ "Quick Guess C": [0.6, 0.7]
+ "NumGuess D": [0.4, 0.6]
+ "GuessIt E": [0.7, 0.5]
+ "Perfect Guess F": [0.6, 0.4]
+ "SmartGuesser G": [0.8, 0.6]
+ "Our Target Product": [0.5, 0.8]
+
+## Requirement Analysis
+
+The game should be simple yet engaging, allowing players of different skill levels to enjoy it. It should provide immediate feedback and track the player's performance. The game should also be designed with a clean and intuitive interface, and it should be easy to add new features in the future.
+
+## Requirement Pool
+
+- ['P0', 'Implement the core game logic to randomly select a number and allow the user to guess it']
+- ['P0', 'Design a user interface that displays the game status and results clearly']
+- ['P1', 'Add difficulty levels by varying the range of possible numbers']
+- ['P1', 'Keep track of and display the number of attempts for each game session']
+- ['P2', "Store and show the history of the player's guesses during a game session"]
+
+## UI Design draft
+
+The UI will feature a clean and minimalist design with a number input field, submit button, and messages area to provide feedback. There will be options to select the difficulty level and a display showing the number of attempts and history of past guesses.
+
+## Anything UNCLEAR"""
+
+DESIGN_SAMPLE = """
+## Implementation approach
+
+We will create a Python-based number guessing game with a simple command-line interface. For the user interface, we will use the built-in 'input' and 'print' functions for interaction. The random library will be used for generating random numbers. We will structure the code to be modular and easily extendable, separating the game logic from the user interface.
+
+## File list
+
+- main.py
+- game.py
+- ui.py
+
+## Data structures and interfaces
+
+
+classDiagram
+ class Game {
+ -int secret_number
+ -int min_range
+ -int max_range
+ -list attempts
+ +__init__(difficulty: str)
+ +start_game()
+ +check_guess(guess: int) str
+ +get_attempts() int
+ +get_history() list
+ }
+ class UI {
+ +start()
+ +display_message(message: str)
+ +get_user_input(prompt: str) str
+ +show_attempts(attempts: int)
+ +show_history(history: list)
+ +select_difficulty() str
+ }
+ class Main {
+ +main()
+ }
+ Main --> UI
+ UI --> Game
+
+
+## Program call flow
+
+
+sequenceDiagram
+ participant M as Main
+ participant UI as UI
+ participant G as Game
+ M->>UI: start()
+ UI->>UI: select_difficulty()
+ UI-->>G: __init__(difficulty)
+ G->>G: start_game()
+ loop Game Loop
+ UI->>UI: get_user_input("Enter your guess:")
+ UI-->>G: check_guess(guess)
+ G->>UI: display_message(feedback)
+ G->>UI: show_attempts(attempts)
+ G->>UI: show_history(history)
+ end
+ G->>UI: display_message("Correct! Game over.")
+ UI->>M: main() # Game session ends
+
+
+## Anything UNCLEAR
+
+The requirement analysis suggests the need for a clean and intuitive interface. Since we are using a command-line interface, we need to ensure that the text-based UI is as user-friendly as possible. Further clarification on whether a graphical user interface (GUI) is expected in the future would be helpful for planning the extendability of the game."""
+
+TASKS_SAMPLE = """
+## Required Python packages
+
+- random==2.2.1
+
+## Required Other language third-party packages
+
+- No third-party dependencies required
+
+## Logic Analysis
+
+- ['game.py', 'Contains Game class with methods __init__, start_game, check_guess, get_attempts, get_history and uses random library for generating secret_number']
+- ['ui.py', 'Contains UI class with methods start, display_message, get_user_input, show_attempts, show_history, select_difficulty and interacts with Game class']
+- ['main.py', 'Contains Main class with method main that initializes UI class and starts the game loop']
+
+## Task list
+
+- game.py
+- ui.py
+- main.py
+
+## Full API spec
+
+
+
+## Shared Knowledge
+
+`game.py` contains the core game logic and is used by `ui.py` to interact with the user. `main.py` serves as the entry point to start the game.
+
+## Anything UNCLEAR
+
+The requirement analysis suggests the need for a clean and intuitive interface. Since we are using a command-line interface, we need to ensure that the text-based UI is as user-friendly as possible. Further clarification on whether a graphical user interface (GUI) is expected in the future would be helpful for planning the extendability of the game."""
+
+OLD_CODE_SAMPLE = """
+--- game.py
+```## game.py
+
+import random
+
+class Game:
+ def __init__(self, difficulty: str = 'medium'):
+ self.min_range, self.max_range = self._set_difficulty(difficulty)
+ self.secret_number = random.randint(self.min_range, self.max_range)
+ self.attempts = []
+
+ def _set_difficulty(self, difficulty: str):
+ difficulties = {
+ 'easy': (1, 10),
+ 'medium': (1, 100),
+ 'hard': (1, 1000)
+ }
+ return difficulties.get(difficulty, (1, 100))
+
+ def start_game(self):
+ self.secret_number = random.randint(self.min_range, self.max_range)
+ self.attempts = []
+
+ def check_guess(self, guess: int) -> str:
+ self.attempts.append(guess)
+ if guess < self.secret_number:
+ return "It's higher."
+ elif guess > self.secret_number:
+ return "It's lower."
+ else:
+ return "Correct! Game over."
+
+ def get_attempts(self) -> int:
+ return len(self.attempts)
+
+ def get_history(self) -> list:
+ return self.attempts```
+
+--- ui.py
+```## ui.py
+
+from game import Game
+
+class UI:
+ def start(self):
+ difficulty = self.select_difficulty()
+ game = Game(difficulty)
+ game.start_game()
+ self.display_welcome_message(game)
+
+ feedback = ""
+ while feedback != "Correct! Game over.":
+ guess = self.get_user_input("Enter your guess: ")
+ if self.is_valid_guess(guess):
+ feedback = game.check_guess(int(guess))
+ self.display_message(feedback)
+ self.show_attempts(game.get_attempts())
+ self.show_history(game.get_history())
+ else:
+ self.display_message("Please enter a valid number.")
+
+ def display_welcome_message(self, game):
+ print("Welcome to the Number Guessing Game!")
+ print(f"Guess the number between {game.min_range} and {game.max_range}.")
+
+ def is_valid_guess(self, guess):
+ return guess.isdigit()
+
+ def display_message(self, message: str):
+ print(message)
+
+ def get_user_input(self, prompt: str) -> str:
+ return input(prompt)
+
+ def show_attempts(self, attempts: int):
+ print(f"Number of attempts: {attempts}")
+
+ def show_history(self, history: list):
+ print("Guess history:")
+ for guess in history:
+ print(guess)
+
+ def select_difficulty(self) -> str:
+ while True:
+ difficulty = input("Select difficulty (easy, medium, hard): ").lower()
+ if difficulty in ['easy', 'medium', 'hard']:
+ return difficulty
+ else:
+ self.display_message("Invalid difficulty. Please choose 'easy', 'medium', or 'hard'.")```
+
+--- main.py
+```## main.py
+
+from ui import UI
+
+class Main:
+ def main(self):
+ user_interface = UI()
+ user_interface.start()
+
+if __name__ == "__main__":
+ main_instance = Main()
+ main_instance.main()```
+"""
+
+REFINED_PRD_JSON = {
+ "Language": "en_us",
+ "Programming Language": "Python",
+ "Refined Requirements": "Adding graphical interface functionality to enhance the user experience in the number-guessing game.",
+ "Project Name": "number_guessing_game",
+ "Refined Product Goals": [
+ "Ensure a user-friendly interface for the game with the new graphical interface",
+ "Provide a challenging yet enjoyable game experience with visual enhancements",
+ "Design the game to be easily extendable for future features, including graphical elements",
+ ],
+ "Refined User Stories": [
+ "As a player, I want to interact with a graphical interface to guess numbers and receive visual feedback on my guesses",
+ "As a player, I want to easily select the difficulty level through the graphical interface",
+ "As a player, I want to visually track my previous guesses and the number of attempts in the graphical interface",
+ "As a player, I want to be congratulated with a visually appealing message when I guess the number correctly",
+ ],
+ "Competitive Analysis": [
+ "Guess The Number Game A: Basic text interface, no difficulty levels",
+ "Number Master B: Has difficulty levels, but cluttered interface",
+ "Quick Guess C: Sleek design, but lacks performance tracking",
+ "NumGuess D: Good performance tracking, but not mobile-friendly",
+ "GuessIt E: Mobile-friendly, but too many ads",
+ "Perfect Guess F: Offers hints, but the hints are not very helpful",
+ "SmartGuesser G: Has a learning mode, but lacks a competitive edge",
+ "Graphical Guess H: Graphical interface, but poor user experience due to complex design",
+ ],
+ "Competitive Quadrant Chart": 'quadrantChart\n title "User Engagement and Game Complexity with Graphical Interface"\n x-axis "Low Complexity" --> "High Complexity"\n y-axis "Low Engagement" --> "High Engagement"\n quadrant-1 "Too Simple"\n quadrant-2 "Niche Appeal"\n quadrant-3 "Complex & Unengaging"\n quadrant-4 "Sweet Spot"\n "Guess The Number Game A": [0.2, 0.4]\n "Number Master B": [0.5, 0.3]\n "Quick Guess C": [0.6, 0.7]\n "NumGuess D": [0.4, 0.6]\n "GuessIt E": [0.7, 0.5]\n "Perfect Guess F": [0.6, 0.4]\n "SmartGuesser G": [0.8, 0.6]\n "Graphical Guess H": [0.7, 0.3]\n "Our Target Product": [0.5, 0.9]',
+ "Refined Requirement Analysis": [
+ "The game should maintain its simplicity while integrating a graphical interface for enhanced engagement.",
+ "Immediate visual feedback is crucial for user satisfaction in the graphical interface.",
+ "The interface must be intuitive, allowing for easy navigation and selection of game options.",
+ "The graphical design should be clean and not detract from the game's core guessing mechanic.",
+ ],
+ "Refined Requirement Pool": [
+ ["P0", "Implement a graphical user interface (GUI) to replace the command-line interaction"],
+ [
+ "P0",
+ "Design a user interface that displays the game status, results, and feedback clearly with graphical elements",
+ ],
+ ["P1", "Incorporate interactive elements for selecting difficulty levels"],
+ ["P1", "Visualize the history of the player's guesses and the number of attempts within the game session"],
+ ["P2", "Create animations for correct or incorrect guesses to enhance user feedback"],
+ ["P2", "Ensure the GUI is responsive and compatible with various screen sizes"],
+ ["P2", "Store and show the history of the player's guesses during a game session"],
+ ],
+ "UI Design draft": "The UI will feature a modern and minimalist design with a graphical number input field, a submit button with animations, and a dedicated area for visual feedback. It will include interactive elements to select the difficulty level and a visual display for the number of attempts and history of past guesses.",
+ "Anything UNCLEAR": "",
+}
+
+REFINED_DESIGN_JSON = {
+ "Refined Implementation Approach": "To accommodate the new graphical user interface (GUI) requirements, we will leverage the Tkinter library, which is included with Python and supports the creation of a user-friendly GUI. The game logic will remain in Python, with Tkinter handling the rendering of the interface. We will ensure that the GUI is responsive and provides immediate visual feedback. The main game loop will be event-driven, responding to user inputs such as button clicks and difficulty selection.",
+ "Refined File list": ["main.py", "game.py", "ui.py", "gui.py"],
+ "Refined Data structures and interfaces": "\nclassDiagram\n class Game {\n -int secret_number\n -int min_range\n -int max_range\n -list attempts\n +__init__(difficulty: str)\n +start_game()\n +check_guess(guess: int) str\n +get_attempts() int\n +get_history() list\n }\n class UI {\n +start()\n +display_message(message: str)\n +get_user_input(prompt: str) str\n +show_attempts(attempts: int)\n +show_history(history: list)\n +select_difficulty() str\n }\n class GUI {\n +__init__()\n +setup_window()\n +bind_events()\n +update_feedback(message: str)\n +update_attempts(attempts: int)\n +update_history(history: list)\n +show_difficulty_selector()\n +animate_guess_result(correct: bool)\n }\n class Main {\n +main()\n }\n Main --> UI\n UI --> Game\n UI --> GUI\n GUI --> Game\n",
+ "Refined Program call flow": '\nsequenceDiagram\n participant M as Main\n participant UI as UI\n participant G as Game\n participant GU as GUI\n M->>UI: start()\n UI->>GU: setup_window()\n GU->>GU: bind_events()\n GU->>UI: select_difficulty()\n UI-->>G: __init__(difficulty)\n G->>G: start_game()\n loop Game Loop\n GU->>GU: show_difficulty_selector()\n GU->>UI: get_user_input("Enter your guess:")\n UI-->>G: check_guess(guess)\n G->>GU: update_feedback(feedback)\n G->>GU: update_attempts(attempts)\n G->>GU: update_history(history)\n GU->>GU: animate_guess_result(correct)\n end\n G->>GU: update_feedback("Correct! Game over.")\n GU->>M: main() # Game session ends\n',
+ "Anything UNCLEAR": "",
+}
+
+REFINED_TASKS_JSON = {
+ "Required Python packages": ["random==2.2.1", "Tkinter==8.6"],
+ "Required Other language third-party packages": ["No third-party dependencies required"],
+ "Refined Logic Analysis": [
+ [
+ "game.py",
+ "Contains Game class with methods __init__, start_game, check_guess, get_attempts, get_history and uses random library for generating secret_number",
+ ],
+ [
+ "ui.py",
+ "Contains UI class with methods start, display_message, get_user_input, show_attempts, show_history, select_difficulty and interacts with Game class",
+ ],
+ [
+ "gui.py",
+ "Contains GUI class with methods __init__, setup_window, bind_events, update_feedback, update_attempts, update_history, show_difficulty_selector, animate_guess_result and interacts with Game class for GUI rendering",
+ ],
+ [
+ "main.py",
+ "Contains Main class with method main that initializes UI class and starts the event-driven game loop",
+ ],
+ ],
+ "Refined Task list": ["game.py", "ui.py", "gui.py", "main.py"],
+ "Full API spec": "",
+ "Refined Shared Knowledge": "`game.py` contains the core game logic and is used by `ui.py` to interact with the user. `main.py` serves as the entry point to start the game. `gui.py` is introduced to handle the graphical user interface using Tkinter, which will interact with both `game.py` and `ui.py` for a responsive and user-friendly experience.",
+ "Anything UNCLEAR": "",
+}
+
+CODE_PLAN_AND_CHANGE_SAMPLE = {
+ "Code Plan And Change": '\n1. Plan for gui.py: Develop the GUI using Tkinter to replace the command-line interface. Start by setting up the main window and event handling. Then, add widgets for displaying the game status, results, and feedback. Implement interactive elements for difficulty selection and visualize the guess history. Finally, create animations for guess feedback and ensure responsiveness across different screen sizes.\n```python\nclass GUI:\n- pass\n+ def __init__(self):\n+ self.setup_window()\n+\n+ def setup_window(self):\n+ # Initialize the main window using Tkinter\n+ pass\n+\n+ def bind_events(self):\n+ # Bind button clicks and other events\n+ pass\n+\n+ def update_feedback(self, message: str):\n+ # Update the feedback label with the given message\n+ pass\n+\n+ def update_attempts(self, attempts: int):\n+ # Update the attempts label with the number of attempts\n+ pass\n+\n+ def update_history(self, history: list):\n+ # Update the history view with the list of past guesses\n+ pass\n+\n+ def show_difficulty_selector(self):\n+ # Show buttons or a dropdown for difficulty selection\n+ pass\n+\n+ def animate_guess_result(self, correct: bool):\n+ # Trigger an animation for correct or incorrect guesses\n+ pass\n```\n\n2. Plan for main.py: Modify the main.py to initialize the GUI and start the event-driven game loop. Ensure that the GUI is the primary interface for user interaction.\n```python\nclass Main:\n def main(self):\n- user_interface = UI()\n- user_interface.start()\n+ graphical_user_interface = GUI()\n+ graphical_user_interface.setup_window()\n+ graphical_user_interface.bind_events()\n+ # Start the Tkinter main loop\n+ pass\n\n if __name__ == "__main__":\n main_instance = Main()\n main_instance.main()\n```\n\n3. Plan for ui.py: Refactor ui.py to work with the new GUI class. Remove command-line interactions and delegate display and input tasks to the GUI.\n```python\nclass UI:\n- def display_message(self, message: str):\n- print(message)\n+\n+ def display_message(self, message: str):\n+ # This method will now pass the message to the GUI to display\n+ pass\n\n- def get_user_input(self, prompt: str) -> str:\n- return input(prompt)\n+\n+ def get_user_input(self, prompt: str) -> str:\n+ # This method will now trigger the GUI to get user input\n+ pass\n\n- def show_attempts(self, attempts: int):\n- print(f"Number of attempts: {attempts}")\n+\n+ def show_attempts(self, attempts: int):\n+ # This method will now update the GUI with the number of attempts\n+ pass\n\n- def show_history(self, history: list):\n- print("Guess history:")\n- for guess in history:\n- print(guess)\n+\n+ def show_history(self, history: list):\n+ # This method will now update the GUI with the guess history\n+ pass\n```\n\n4. Plan for game.py: Ensure game.py remains mostly unchanged as it contains the core game logic. However, make minor adjustments if necessary to integrate with the new GUI.\n```python\nclass Game:\n # No changes required for now\n```\n'
+}
+
+REFINED_CODE_INPUT_SAMPLE = """
+-----Now, game.py to be rewritten
+```## game.py
+
+import random
+
+class Game:
+ def __init__(self, difficulty: str = 'medium'):
+ self.min_range, self.max_range = self._set_difficulty(difficulty)
+ self.secret_number = random.randint(self.min_range, self.max_range)
+ self.attempts = []
+
+ def _set_difficulty(self, difficulty: str):
+ difficulties = {
+ 'easy': (1, 10),
+ 'medium': (1, 100),
+ 'hard': (1, 1000)
+ }
+ return difficulties.get(difficulty, (1, 100))
+
+ def start_game(self):
+ self.secret_number = random.randint(self.min_range, self.max_range)
+ self.attempts = []
+
+ def check_guess(self, guess: int) -> str:
+ self.attempts.append(guess)
+ if guess < self.secret_number:
+ return "It's higher."
+ elif guess > self.secret_number:
+ return "It's lower."
+ else:
+ return "Correct! Game over."
+
+ def get_attempts(self) -> int:
+ return len(self.attempts)
+
+ def get_history(self) -> list:
+ return self.attempts```
+"""
+
+REFINED_CODE_SAMPLE = """
+## game.py
+
+import random
+
+class Game:
+ def __init__(self, difficulty: str = 'medium'):
+ # Set the difficulty level with default value 'medium'
+ self.min_range, self.max_range = self._set_difficulty(difficulty)
+ # Initialize the secret number based on the difficulty
+ self.secret_number = random.randint(self.min_range, self.max_range)
+ # Initialize the list to keep track of attempts
+ self.attempts = []
+
+ def _set_difficulty(self, difficulty: str):
+ # Define the range of numbers for each difficulty level
+ difficulties = {
+ 'easy': (1, 10),
+ 'medium': (1, 100),
+ 'hard': (1, 1000)
+ }
+ # Return the corresponding range for the selected difficulty, default to 'medium' if not found
+ return difficulties.get(difficulty, (1, 100))
+
+ def start_game(self):
+ # Reset the secret number and attempts list for a new game
+ self.secret_number = random.randint(self.min_range, self.max_range)
+ self.attempts.clear()
+
+ def check_guess(self, guess: int) -> str:
+ # Add the guess to the attempts list
+ self.attempts.append(guess)
+ # Provide feedback based on the guess
+ if guess < self.secret_number:
+ return "It's higher."
+ elif guess > self.secret_number:
+ return "It's lower."
+ else:
+ return "Correct! Game over."
+
+ def get_attempts(self) -> int:
+ # Return the number of attempts made
+ return len(self.attempts)
+
+ def get_history(self) -> list:
+ # Return the list of attempts made
+ return self.attempts
+"""
diff --git a/tests/data/incremental_dev_project/number_guessing_game.zip b/tests/data/incremental_dev_project/number_guessing_game.zip
new file mode 100644
index 000000000..7bbe07713
Binary files /dev/null and b/tests/data/incremental_dev_project/number_guessing_game.zip differ
diff --git a/tests/data/incremental_dev_project/pygame_2048.zip b/tests/data/incremental_dev_project/pygame_2048.zip
new file mode 100644
index 000000000..93e9cf0fe
Binary files /dev/null and b/tests/data/incremental_dev_project/pygame_2048.zip differ
diff --git a/tests/data/incremental_dev_project/readme.md b/tests/data/incremental_dev_project/readme.md
new file mode 100644
index 000000000..231589028
--- /dev/null
+++ b/tests/data/incremental_dev_project/readme.md
@@ -0,0 +1,3 @@
+# Code archive
+
+This folder contains a compressed package for the test_incremental_dev.py file, which is used to demonstrate the process of incremental development.
diff --git a/tests/data/incremental_dev_project/simple_add_calculator.zip b/tests/data/incremental_dev_project/simple_add_calculator.zip
new file mode 100644
index 000000000..e6e73f8f9
Binary files /dev/null and b/tests/data/incremental_dev_project/simple_add_calculator.zip differ
diff --git a/tests/data/incremental_dev_project/snake_game.zip b/tests/data/incremental_dev_project/snake_game.zip
new file mode 100644
index 000000000..997203a18
Binary files /dev/null and b/tests/data/incremental_dev_project/snake_game.zip differ
diff --git a/tests/data/incremental_dev_project/word_cloud.zip b/tests/data/incremental_dev_project/word_cloud.zip
new file mode 100644
index 000000000..d8747d14d
Binary files /dev/null and b/tests/data/incremental_dev_project/word_cloud.zip differ
diff --git a/tests/data/ml_datasets/titanic/split_eval.csv b/tests/data/ml_datasets/titanic/split_eval.csv
new file mode 100644
index 000000000..6da6ff6b3
--- /dev/null
+++ b/tests/data/ml_datasets/titanic/split_eval.csv
@@ -0,0 +1,180 @@
+PassengerId,Survived,Pclass,Name,Sex,Age,SibSp,Parch,Ticket,Fare,Cabin,Embarked
+206,0,3,"Strom, Miss. Telma Matilda",female,2.0,0,1,347054,10.4625,G6,S
+45,1,3,"Devaney, Miss. Margaret Delia",female,19.0,0,0,330958,7.8792,,Q
+822,1,3,"Lulic, Mr. Nikola",male,27.0,0,0,315098,8.6625,,S
+459,1,2,"Toomey, Miss. Ellen",female,50.0,0,0,F.C.C. 13531,10.5,,S
+796,0,2,"Otter, Mr. Richard",male,39.0,0,0,28213,13.0,,S
+119,0,1,"Baxter, Mr. Quigg Edmond",male,24.0,0,1,PC 17558,247.5208,B58 B60,C
+425,0,3,"Rosblom, Mr. Viktor Richard",male,18.0,1,1,370129,20.2125,,S
+679,0,3,"Goodwin, Mrs. Frederick (Augusta Tyler)",female,43.0,1,6,CA 2144,46.9,,S
+270,1,1,"Bissette, Miss. Amelia",female,35.0,0,0,PC 17760,135.6333,C99,S
+230,0,3,"Lefebre, Miss. Mathilde",female,,3,1,4133,25.4667,,S
+690,1,1,"Madill, Miss. Georgette Alexandra",female,15.0,0,1,24160,211.3375,B5,S
+321,0,3,"Dennis, Mr. Samuel",male,22.0,0,0,A/5 21172,7.25,,S
+406,0,2,"Gale, Mr. Shadrach",male,34.0,1,0,28664,21.0,,S
+41,0,3,"Ahlin, Mrs. Johan (Johanna Persdotter Larsson)",female,40.0,1,0,7546,9.475,,S
+25,0,3,"Palsson, Miss. Torborg Danira",female,8.0,3,1,349909,21.075,,S
+554,1,3,"Leeni, Mr. Fahim (""Philip Zenni"")",male,22.0,0,0,2620,7.225,,C
+413,1,1,"Minahan, Miss. Daisy E",female,33.0,1,0,19928,90.0,C78,Q
+513,1,1,"McGough, Mr. James Robert",male,36.0,0,0,PC 17473,26.2875,E25,S
+756,1,2,"Hamalainen, Master. Viljo",male,0.67,1,1,250649,14.5,,S
+392,1,3,"Jansson, Mr. Carl Olof",male,21.0,0,0,350034,7.7958,,S
+602,0,3,"Slabenoff, Mr. Petco",male,,0,0,349214,7.8958,,S
+326,1,1,"Young, Miss. Marie Grice",female,36.0,0,0,PC 17760,135.6333,C32,C
+373,0,3,"Beavan, Mr. William Thomas",male,19.0,0,0,323951,8.05,,S
+377,1,3,"Landergren, Miss. Aurora Adelia",female,22.0,0,0,C 7077,7.25,,S
+201,0,3,"Vande Walle, Mr. Nestor Cyriel",male,28.0,0,0,345770,9.5,,S
+512,0,3,"Webber, Mr. James",male,,0,0,SOTON/OQ 3101316,8.05,,S
+601,1,2,"Jacobsohn, Mrs. Sidney Samuel (Amy Frances Christy)",female,24.0,2,1,243847,27.0,,S
+631,1,1,"Barkworth, Mr. Algernon Henry Wilson",male,80.0,0,0,27042,30.0,A23,S
+364,0,3,"Asim, Mr. Adola",male,35.0,0,0,SOTON/O.Q. 3101310,7.05,,S
+144,0,3,"Burke, Mr. Jeremiah",male,19.0,0,0,365222,6.75,,Q
+202,0,3,"Sage, Mr. Frederick",male,,8,2,CA. 2343,69.55,,S
+134,1,2,"Weisz, Mrs. Leopold (Mathilde Francoise Pede)",female,29.0,1,0,228414,26.0,,S
+431,1,1,"Bjornstrom-Steffansson, Mr. Mauritz Hakan",male,28.0,0,0,110564,26.55,C52,S
+419,0,2,"Matthews, Mr. William John",male,30.0,0,0,28228,13.0,,S
+782,1,1,"Dick, Mrs. Albert Adrian (Vera Gillespie)",female,17.0,1,0,17474,57.0,B20,S
+705,0,3,"Hansen, Mr. Henrik Juul",male,26.0,1,0,350025,7.8542,,S
+536,1,2,"Hart, Miss. Eva Miriam",female,7.0,0,2,F.C.C. 13529,26.25,,S
+335,1,1,"Frauenthal, Mrs. Henry William (Clara Heinsheimer)",female,,1,0,PC 17611,133.65,,S
+273,1,2,"Mellinger, Mrs. (Elizabeth Anne Maidment)",female,41.0,0,1,250644,19.5,,S
+108,1,3,"Moss, Mr. Albert Johan",male,,0,0,312991,7.775,,S
+403,0,3,"Jussila, Miss. Mari Aina",female,21.0,1,0,4137,9.825,,S
+307,1,1,"Fleming, Miss. Margaret",female,,0,0,17421,110.8833,,C
+218,0,2,"Jacobsohn, Mr. Sidney Samuel",male,42.0,1,0,243847,27.0,,S
+789,1,3,"Dean, Master. Bertram Vere",male,1.0,1,2,C.A. 2315,20.575,,S
+160,0,3,"Sage, Master. Thomas Henry",male,,8,2,CA. 2343,69.55,,S
+20,1,3,"Masselmani, Mrs. Fatima",female,,0,0,2649,7.225,,C
+174,0,3,"Sivola, Mr. Antti Wilhelm",male,21.0,0,0,STON/O 2. 3101280,7.925,,S
+311,1,1,"Hays, Miss. Margaret Bechstein",female,24.0,0,0,11767,83.1583,C54,C
+595,0,2,"Chapman, Mr. John Henry",male,37.0,1,0,SC/AH 29037,26.0,,S
+592,1,1,"Stephenson, Mrs. Walter Bertram (Martha Eustis)",female,52.0,1,0,36947,78.2667,D20,C
+164,0,3,"Calic, Mr. Jovo",male,17.0,0,0,315093,8.6625,,S
+563,0,2,"Norman, Mr. Robert Douglas",male,28.0,0,0,218629,13.5,,S
+172,0,3,"Rice, Master. Arthur",male,4.0,4,1,382652,29.125,,Q
+871,0,3,"Balkic, Mr. Cerin",male,26.0,0,0,349248,7.8958,,S
+176,0,3,"Klasen, Mr. Klas Albin",male,18.0,1,1,350404,7.8542,,S
+434,0,3,"Kallio, Mr. Nikolai Erland",male,17.0,0,0,STON/O 2. 3101274,7.125,,S
+462,0,3,"Morley, Mr. William",male,34.0,0,0,364506,8.05,,S
+49,0,3,"Samaan, Mr. Youssef",male,,2,0,2662,21.6792,,C
+126,1,3,"Nicola-Yarred, Master. Elias",male,12.0,1,0,2651,11.2417,,C
+125,0,1,"White, Mr. Percival Wayland",male,54.0,0,1,35281,77.2875,D26,S
+266,0,2,"Reeves, Mr. David",male,36.0,0,0,C.A. 17248,10.5,,S
+550,1,2,"Davies, Master. John Morgan Jr",male,8.0,1,1,C.A. 33112,36.75,,S
+589,0,3,"Gilinski, Mr. Eliezer",male,22.0,0,0,14973,8.05,,S
+779,0,3,"Kilgannon, Mr. Thomas J",male,,0,0,36865,7.7375,,Q
+179,0,2,"Hale, Mr. Reginald",male,30.0,0,0,250653,13.0,,S
+107,1,3,"Salkjelsvik, Miss. Anna Kristine",female,21.0,0,0,343120,7.65,,S
+624,0,3,"Hansen, Mr. Henry Damsgaard",male,21.0,0,0,350029,7.8542,,S
+115,0,3,"Attalah, Miss. Malake",female,17.0,0,0,2627,14.4583,,C
+42,0,2,"Turpin, Mrs. William John Robert (Dorothy Ann Wonnacott)",female,27.0,1,0,11668,21.0,,S
+664,0,3,"Coleff, Mr. Peju",male,36.0,0,0,349210,7.4958,,S
+661,1,1,"Frauenthal, Dr. Henry William",male,50.0,2,0,PC 17611,133.65,,S
+762,0,3,"Nirva, Mr. Iisakki Antino Aijo",male,41.0,0,0,SOTON/O2 3101272,7.125,,S
+580,1,3,"Jussila, Mr. Eiriik",male,32.0,0,0,STON/O 2. 3101286,7.925,,S
+265,0,3,"Henry, Miss. Delia",female,,0,0,382649,7.75,,Q
+757,0,3,"Carlsson, Mr. August Sigfrid",male,28.0,0,0,350042,7.7958,,S
+666,0,2,"Hickman, Mr. Lewis",male,32.0,2,0,S.O.C. 14879,73.5,,S
+634,0,1,"Parr, Mr. William Henry Marsh",male,,0,0,112052,0.0,,S
+532,0,3,"Toufik, Mr. Nakli",male,,0,0,2641,7.2292,,C
+640,0,3,"Thorneycroft, Mr. Percival",male,,1,0,376564,16.1,,S
+599,0,3,"Boulos, Mr. Hanna",male,,0,0,2664,7.225,,C
+220,0,2,"Harris, Mr. Walter",male,30.0,0,0,W/C 14208,10.5,,S
+150,0,2,"Byles, Rev. Thomas Roussel Davids",male,42.0,0,0,244310,13.0,,S
+269,1,1,"Graham, Mrs. William Thompson (Edith Junkins)",female,58.0,0,1,PC 17582,153.4625,C125,S
+670,1,1,"Taylor, Mrs. Elmer Zebley (Juliet Cummins Wright)",female,,1,0,19996,52.0,C126,S
+578,1,1,"Silvey, Mrs. William Baird (Alice Munger)",female,39.0,1,0,13507,55.9,E44,S
+786,0,3,"Harmer, Mr. Abraham (David Lishin)",male,25.0,0,0,374887,7.25,,S
+82,1,3,"Sheerlinck, Mr. Jan Baptist",male,29.0,0,0,345779,9.5,,S
+400,1,2,"Trout, Mrs. William H (Jessie L)",female,28.0,0,0,240929,12.65,,S
+135,0,2,"Sobey, Mr. Samuel James Hayden",male,25.0,0,0,C.A. 29178,13.0,,S
+223,0,3,"Green, Mr. George Henry",male,51.0,0,0,21440,8.05,,S
+693,1,3,"Lam, Mr. Ali",male,,0,0,1601,56.4958,,S
+280,1,3,"Abbott, Mrs. Stanton (Rosa Hunt)",female,35.0,1,1,C.A. 2673,20.25,,S
+102,0,3,"Petroff, Mr. Pastcho (""Pentcho"")",male,,0,0,349215,7.8958,,S
+288,0,3,"Naidenoff, Mr. Penko",male,22.0,0,0,349206,7.8958,,S
+711,1,1,"Mayne, Mlle. Berthe Antonine (""Mrs de Villiers"")",female,24.0,0,0,PC 17482,49.5042,C90,C
+256,1,3,"Touma, Mrs. Darwis (Hanne Youssef Razi)",female,29.0,0,2,2650,15.2458,,C
+23,1,3,"McGowan, Miss. Anna ""Annie""",female,15.0,0,0,330923,8.0292,,Q
+582,1,1,"Thayer, Mrs. John Borland (Marian Longstreth Morris)",female,39.0,1,1,17421,110.8833,C68,C
+564,0,3,"Simmons, Mr. John",male,,0,0,SOTON/OQ 392082,8.05,,S
+405,0,3,"Oreskovic, Miss. Marija",female,20.0,0,0,315096,8.6625,,S
+429,0,3,"Flynn, Mr. James",male,,0,0,364851,7.75,,Q
+848,0,3,"Markoff, Mr. Marin",male,35.0,0,0,349213,7.8958,,C
+726,0,3,"Oreskovic, Mr. Luka",male,20.0,0,0,315094,8.6625,,S
+721,1,2,"Harper, Miss. Annie Jessie ""Nina""",female,6.0,0,1,248727,33.0,,S
+637,0,3,"Leinonen, Mr. Antti Gustaf",male,32.0,0,0,STON/O 2. 3101292,7.925,,S
+863,1,1,"Swift, Mrs. Frederick Joel (Margaret Welles Barron)",female,48.0,0,0,17466,25.9292,D17,S
+615,0,3,"Brocklebank, Mr. William Alfred",male,35.0,0,0,364512,8.05,,S
+199,1,3,"Madigan, Miss. Margaret ""Maggie""",female,,0,0,370370,7.75,,Q
+787,1,3,"Sjoblom, Miss. Anna Sofia",female,18.0,0,0,3101265,7.4958,,S
+156,0,1,"Williams, Mr. Charles Duane",male,51.0,0,1,PC 17597,61.3792,,C
+190,0,3,"Turcin, Mr. Stjepan",male,36.0,0,0,349247,7.8958,,S
+556,0,1,"Wright, Mr. George",male,62.0,0,0,113807,26.55,,S
+890,1,1,"Behr, Mr. Karl Howell",male,26.0,0,0,111369,30.0,C148,C
+827,0,3,"Lam, Mr. Len",male,,0,0,1601,56.4958,,S
+534,1,3,"Peter, Mrs. Catherine (Catherine Rizk)",female,,0,2,2668,22.3583,,C
+834,0,3,"Augustsson, Mr. Albert",male,23.0,0,0,347468,7.8542,,S
+279,0,3,"Rice, Master. Eric",male,7.0,4,1,382652,29.125,,Q
+189,0,3,"Bourke, Mr. John",male,40.0,1,1,364849,15.5,,Q
+561,0,3,"Morrow, Mr. Thomas Rowan",male,,0,0,372622,7.75,,Q
+375,0,3,"Palsson, Miss. Stina Viola",female,3.0,3,1,349909,21.075,,S
+322,0,3,"Danoff, Mr. Yoto",male,27.0,0,0,349219,7.8958,,S
+158,0,3,"Corn, Mr. Harry",male,30.0,0,0,SOTON/OQ 392090,8.05,,S
+524,1,1,"Hippach, Mrs. Louis Albert (Ida Sophia Fischer)",female,44.0,0,1,111361,57.9792,B18,C
+175,0,1,"Smith, Mr. James Clinch",male,56.0,0,0,17764,30.6958,A7,C
+117,0,3,"Connors, Mr. Patrick",male,70.5,0,0,370369,7.75,,Q
+810,1,1,"Chambers, Mrs. Norman Campbell (Bertha Griggs)",female,33.0,1,0,113806,53.1,E8,S
+472,0,3,"Cacic, Mr. Luka",male,38.0,0,0,315089,8.6625,,S
+228,0,3,"Lovell, Mr. John Hall (""Henry"")",male,20.5,0,0,A/5 21173,7.25,,S
+330,1,1,"Hippach, Miss. Jean Gertrude",female,16.0,0,1,111361,57.9792,B18,C
+147,1,3,"Andersson, Mr. August Edvard (""Wennerstrom"")",male,27.0,0,0,350043,7.7958,,S
+98,1,1,"Greenfield, Mr. William Bertram",male,23.0,0,1,PC 17759,63.3583,D10 D12,C
+493,0,1,"Molson, Mr. Harry Markland",male,55.0,0,0,113787,30.5,C30,S
+73,0,2,"Hood, Mr. Ambrose Jr",male,21.0,0,0,S.O.C. 14879,73.5,,S
+645,1,3,"Baclini, Miss. Eugenie",female,0.75,2,1,2666,19.2583,,C
+303,0,3,"Johnson, Mr. William Cahoone Jr",male,19.0,0,0,LINE,0.0,,S
+699,0,1,"Thayer, Mr. John Borland",male,49.0,1,1,17421,110.8833,C68,C
+704,0,3,"Gallagher, Mr. Martin",male,25.0,0,0,36864,7.7417,,Q
+639,0,3,"Panula, Mrs. Juha (Maria Emilia Ojala)",female,41.0,0,5,3101295,39.6875,,S
+99,1,2,"Doling, Mrs. John T (Ada Julia Bone)",female,34.0,0,1,231919,23.0,,S
+74,0,3,"Chronopoulos, Mr. Apostolos",male,26.0,1,0,2680,14.4542,,C
+157,1,3,"Gilnagh, Miss. Katherine ""Katie""",female,16.0,0,0,35851,7.7333,,Q
+475,0,3,"Strandberg, Miss. Ida Sofia",female,22.0,0,0,7553,9.8375,,S
+240,0,2,"Hunt, Mr. George Henry",male,33.0,0,0,SCO/W 1585,12.275,,S
+801,0,2,"Ponesell, Mr. Martin",male,34.0,0,0,250647,13.0,,S
+829,1,3,"McCormack, Mr. Thomas Joseph",male,,0,0,367228,7.75,,Q
+208,1,3,"Albimona, Mr. Nassef Cassem",male,26.0,0,0,2699,18.7875,,C
+29,1,3,"O'Dwyer, Miss. Ellen ""Nellie""",female,,0,0,330959,7.8792,,Q
+616,1,2,"Herman, Miss. Alice",female,24.0,1,2,220845,65.0,,S
+309,0,2,"Abelson, Mr. Samuel",male,30.0,1,0,P/PP 3381,24.0,,C
+382,1,3,"Nakid, Miss. Maria (""Mary"")",female,1.0,0,2,2653,15.7417,,C
+703,0,3,"Barbara, Miss. Saiide",female,18.0,0,1,2691,14.4542,,C
+623,1,3,"Nakid, Mr. Sahid",male,20.0,1,1,2653,15.7417,,C
+26,1,3,"Asplund, Mrs. Carl Oscar (Selma Augusta Emilia Johansson)",female,38.0,1,5,347077,31.3875,,S
+519,1,2,"Angle, Mrs. William A (Florence ""Mary"" Agnes Hughes)",female,36.0,1,0,226875,26.0,,S
+638,0,2,"Collyer, Mr. Harvey",male,31.0,1,1,C.A. 31921,26.25,,S
+360,1,3,"Mockler, Miss. Helen Mary ""Ellie""",female,,0,0,330980,7.8792,,Q
+736,0,3,"Williams, Mr. Leslie",male,28.5,0,0,54636,16.1,,S
+101,0,3,"Petranec, Miss. Matilda",female,28.0,0,0,349245,7.8958,,S
+165,0,3,"Panula, Master. Eino Viljami",male,1.0,4,1,3101295,39.6875,,S
+591,0,3,"Rintamaki, Mr. Matti",male,35.0,0,0,STON/O 2. 3101273,7.125,,S
+11,1,3,"Sandstrom, Miss. Marguerite Rut",female,4.0,1,1,PP 9549,16.7,G6,S
+217,1,3,"Honkanen, Miss. Eliina",female,27.0,0,0,STON/O2. 3101283,7.925,,S
+734,0,2,"Berriman, Mr. William John",male,23.0,0,0,28425,13.0,,S
+385,0,3,"Plotcharsky, Mr. Vasil",male,,0,0,349227,7.8958,,S
+854,1,1,"Lines, Miss. Mary Conover",female,16.0,0,1,PC 17592,39.4,D28,S
+860,0,3,"Razi, Mr. Raihed",male,,0,0,2629,7.2292,,C
+359,1,3,"McGovern, Miss. Mary",female,,0,0,330931,7.8792,,Q
+448,1,1,"Seward, Mr. Frederic Kimber",male,34.0,0,0,113794,26.55,,S
+214,0,2,"Givard, Mr. Hans Kristensen",male,30.0,0,0,250646,13.0,,S
+652,1,2,"Doling, Miss. Elsie",female,18.0,0,1,231919,23.0,,S
+192,0,2,"Carbines, Mr. William",male,19.0,0,0,28424,13.0,,S
+57,1,2,"Rugg, Miss. Emily",female,21.0,0,0,C.A. 31026,10.5,,S
+868,0,1,"Roebling, Mr. Washington Augustus II",male,31.0,0,0,PC 17590,50.4958,A24,S
+531,1,2,"Quick, Miss. Phyllis May",female,2.0,1,1,26360,26.0,,S
+248,1,2,"Hamalainen, Mrs. William (Anna)",female,24.0,0,2,250649,14.5,,S
+260,1,2,"Parrish, Mrs. (Lutie Davis)",female,50.0,0,1,230433,26.0,,S
+354,0,3,"Arnold-Franchi, Mr. Josef",male,25.0,1,0,349237,17.8,,S
+784,0,3,"Johnston, Mr. Andrew G",male,,1,2,W./C. 6607,23.45,,S
+853,0,3,"Boulos, Miss. Nourelain",female,9.0,1,1,2678,15.2458,,C
diff --git a/tests/data/ml_datasets/titanic/split_train.csv b/tests/data/ml_datasets/titanic/split_train.csv
new file mode 100644
index 000000000..a48680208
--- /dev/null
+++ b/tests/data/ml_datasets/titanic/split_train.csv
@@ -0,0 +1,713 @@
+PassengerId,Survived,Pclass,Name,Sex,Age,SibSp,Parch,Ticket,Fare,Cabin,Embarked
+409,0,3,"Birkeland, Mr. Hans Martin Monsen",male,21.0,0,0,312992,7.775,,S
+481,0,3,"Goodwin, Master. Harold Victor",male,9.0,5,2,CA 2144,46.9,,S
+511,1,3,"Daly, Mr. Eugene Patrick",male,29.0,0,0,382651,7.75,,Q
+610,1,1,"Shutes, Miss. Elizabeth W",female,40.0,0,0,PC 17582,153.4625,C125,S
+548,1,2,"Padro y Manent, Mr. Julian",male,,0,0,SC/PARIS 2146,13.8625,,C
+710,1,3,"Moubarek, Master. Halim Gonios (""William George"")",male,,1,1,2661,15.2458,,C
+153,0,3,"Meo, Mr. Alfonzo",male,55.5,0,0,A.5. 11206,8.05,,S
+494,0,1,"Artagaveytia, Mr. Ramon",male,71.0,0,0,PC 17609,49.5042,,C
+393,0,3,"Gustafsson, Mr. Johan Birger",male,28.0,2,0,3101277,7.925,,S
+824,1,3,"Moor, Mrs. (Beila)",female,27.0,0,1,392096,12.475,E121,S
+577,1,2,"Garside, Miss. Ethel",female,34.0,0,0,243880,13.0,,S
+773,0,2,"Mack, Mrs. (Mary)",female,57.0,0,0,S.O./P.P. 3,10.5,E77,S
+745,1,3,"Stranden, Mr. Juho",male,31.0,0,0,STON/O 2. 3101288,7.925,,S
+328,1,2,"Ball, Mrs. (Ada E Hall)",female,36.0,0,0,28551,13.0,D,S
+460,0,3,"O'Connor, Mr. Maurice",male,,0,0,371060,7.75,,Q
+222,0,2,"Bracken, Mr. James H",male,27.0,0,0,220367,13.0,,S
+851,0,3,"Andersson, Master. Sigvard Harald Elias",male,4.0,4,2,347082,31.275,,S
+558,0,1,"Robbins, Mr. Victor",male,,0,0,PC 17757,227.525,,C
+47,0,3,"Lennon, Mr. Denis",male,,1,0,370371,15.5,,Q
+449,1,3,"Baclini, Miss. Marie Catherine",female,5.0,2,1,2666,19.2583,,C
+371,1,1,"Harder, Mr. George Achilles",male,25.0,1,0,11765,55.4417,E50,C
+196,1,1,"Lurette, Miss. Elise",female,58.0,0,0,PC 17569,146.5208,B80,C
+761,0,3,"Garfirth, Mr. John",male,,0,0,358585,14.5,,S
+55,0,1,"Ostby, Mr. Engelhart Cornelius",male,65.0,0,1,113509,61.9792,B30,C
+573,1,1,"Flynn, Mr. John Irwin (""Irving"")",male,36.0,0,0,PC 17474,26.3875,E25,S
+379,0,3,"Betros, Mr. Tannous",male,20.0,0,0,2648,4.0125,,C
+198,0,3,"Olsen, Mr. Karl Siegwart Andreas",male,42.0,0,1,4579,8.4042,,S
+396,0,3,"Johansson, Mr. Erik",male,22.0,0,0,350052,7.7958,,S
+111,0,1,"Porter, Mr. Walter Chamberlain",male,47.0,0,0,110465,52.0,C110,S
+138,0,1,"Futrelle, Mr. Jacques Heath",male,37.0,1,0,113803,53.1,C123,S
+312,1,1,"Ryerson, Miss. Emily Borie",female,18.0,2,2,PC 17608,262.375,B57 B59 B63 B66,C
+391,1,1,"Carter, Mr. William Ernest",male,36.0,1,2,113760,120.0,B96 B98,S
+24,1,1,"Sloper, Mr. William Thompson",male,28.0,0,0,113788,35.5,A6,S
+818,0,2,"Mallet, Mr. Albert",male,31.0,1,1,S.C./PARIS 2079,37.0042,,C
+110,1,3,"Moran, Miss. Bertha",female,,1,0,371110,24.15,,Q
+302,1,3,"McCoy, Mr. Bernard",male,,2,0,367226,23.25,,Q
+104,0,3,"Johansson, Mr. Gustaf Joel",male,33.0,0,0,7540,8.6542,,S
+875,1,2,"Abelson, Mrs. Samuel (Hannah Wizosky)",female,28.0,1,0,P/PP 3381,24.0,,C
+62,1,1,"Icard, Miss. Amelie",female,38.0,0,0,113572,80.0,B28,
+154,0,3,"van Billiard, Mr. Austin Blyler",male,40.5,0,2,A/5. 851,14.5,,S
+289,1,2,"Hosono, Mr. Masabumi",male,42.0,0,0,237798,13.0,,S
+245,0,3,"Attalah, Mr. Sleiman",male,30.0,0,0,2694,7.225,,C
+681,0,3,"Peters, Miss. Katie",female,,0,0,330935,8.1375,,Q
+797,1,1,"Leader, Dr. Alice (Farnham)",female,49.0,0,0,17465,25.9292,D17,S
+226,0,3,"Berglund, Mr. Karl Ivar Sven",male,22.0,0,0,PP 4348,9.35,,S
+857,1,1,"Wick, Mrs. George Dennick (Mary Hitchcock)",female,45.0,1,1,36928,164.8667,,S
+621,0,3,"Yasbeck, Mr. Antoni",male,27.0,1,0,2659,14.4542,,C
+451,0,2,"West, Mr. Edwy Arthur",male,36.0,1,2,C.A. 34651,27.75,,S
+424,0,3,"Danbom, Mrs. Ernst Gilbert (Anna Sigrid Maria Brogren)",female,28.0,1,1,347080,14.4,,S
+450,1,1,"Peuchen, Major. Arthur Godfrey",male,52.0,0,0,113786,30.5,C104,S
+161,0,3,"Cribb, Mr. John Hatfield",male,44.0,0,1,371362,16.1,,S
+743,1,1,"Ryerson, Miss. Susan Parker ""Suzette""",female,21.0,2,2,PC 17608,262.375,B57 B59 B63 B66,C
+651,0,3,"Mitkoff, Mr. Mito",male,,0,0,349221,7.8958,,S
+250,0,2,"Carter, Rev. Ernest Courtenay",male,54.0,1,0,244252,26.0,,S
+540,1,1,"Frolicher, Miss. Hedwig Margaritha",female,22.0,0,2,13568,49.5,B39,C
+414,0,2,"Cunningham, Mr. Alfred Fleming",male,,0,0,239853,0.0,,S
+207,0,3,"Backstrom, Mr. Karl Alfred",male,32.0,1,0,3101278,15.85,,S
+828,1,2,"Mallet, Master. Andre",male,1.0,0,2,S.C./PARIS 2079,37.0042,,C
+484,1,3,"Turkula, Mrs. (Hedwig)",female,63.0,0,0,4134,9.5875,,S
+607,0,3,"Karaic, Mr. Milan",male,30.0,0,0,349246,7.8958,,S
+185,1,3,"Kink-Heilmann, Miss. Luise Gretchen",female,4.0,0,2,315153,22.025,,S
+683,0,3,"Olsvigen, Mr. Thor Anderson",male,20.0,0,0,6563,9.225,,S
+794,0,1,"Hoyt, Mr. William Fisher",male,,0,0,PC 17600,30.6958,,C
+13,0,3,"Saundercock, Mr. William Henry",male,20.0,0,0,A/5. 2151,8.05,,S
+118,0,2,"Turpin, Mr. William John Robert",male,29.0,1,0,11668,21.0,,S
+483,0,3,"Rouse, Mr. Richard Henry",male,50.0,0,0,A/5 3594,8.05,,S
+421,0,3,"Gheorgheff, Mr. Stanio",male,,0,0,349254,7.8958,,C
+543,0,3,"Andersson, Miss. Sigrid Elisabeth",female,11.0,4,2,347082,31.275,,S
+884,0,2,"Banfield, Mr. Frederick James",male,28.0,0,0,C.A./SOTON 34068,10.5,,S
+877,0,3,"Gustafsson, Mr. Alfred Ossian",male,20.0,0,0,7534,9.8458,,S
+109,0,3,"Rekic, Mr. Tido",male,38.0,0,0,349249,7.8958,,S
+603,0,1,"Harrington, Mr. Charles H",male,,0,0,113796,42.4,,S
+575,0,3,"Rush, Mr. Alfred George John",male,16.0,0,0,A/4. 20589,8.05,,S
+253,0,1,"Stead, Mr. William Thomas",male,62.0,0,0,113514,26.55,C87,S
+712,0,1,"Klaber, Mr. Herman",male,,0,0,113028,26.55,C124,S
+397,0,3,"Olsson, Miss. Elina",female,31.0,0,0,350407,7.8542,,S
+194,1,2,"Navratil, Master. Michel M",male,3.0,1,1,230080,26.0,F2,S
+567,0,3,"Stoytcheff, Mr. Ilia",male,19.0,0,0,349205,7.8958,,S
+204,0,3,"Youseff, Mr. Gerious",male,45.5,0,0,2628,7.225,,C
+491,0,3,"Hagland, Mr. Konrad Mathias Reiersen",male,,1,0,65304,19.9667,,S
+815,0,3,"Tomlin, Mr. Ernest Portage",male,30.5,0,0,364499,8.05,,S
+219,1,1,"Bazzani, Miss. Albina",female,32.0,0,0,11813,76.2917,D15,C
+446,1,1,"Dodge, Master. Washington",male,4.0,0,2,33638,81.8583,A34,S
+490,1,3,"Coutts, Master. Eden Leslie ""Neville""",male,9.0,1,1,C.A. 37671,15.9,,S
+112,0,3,"Zabour, Miss. Hileni",female,14.5,1,0,2665,14.4542,,C
+731,1,1,"Allen, Miss. Elisabeth Walton",female,29.0,0,0,24160,211.3375,B5,S
+106,0,3,"Mionoff, Mr. Stoytcho",male,28.0,0,0,349207,7.8958,,S
+480,1,3,"Hirvonen, Miss. Hildur E",female,2.0,0,1,3101298,12.2875,,S
+278,0,2,"Parkes, Mr. Francis ""Frank""",male,,0,0,239853,0.0,,S
+70,0,3,"Kink, Mr. Vincenz",male,26.0,2,0,315151,8.6625,,S
+86,1,3,"Backstrom, Mrs. Karl Alfred (Maria Mathilda Gustafsson)",female,33.0,3,0,3101278,15.85,,S
+795,0,3,"Dantcheff, Mr. Ristiu",male,25.0,0,0,349203,7.8958,,S
+162,1,2,"Watt, Mrs. James (Elizabeth ""Bessie"" Inglis Milne)",female,40.0,0,0,C.A. 33595,15.75,,S
+816,0,1,"Fry, Mr. Richard",male,,0,0,112058,0.0,B102,S
+517,1,2,"Lemore, Mrs. (Amelia Milley)",female,34.0,0,0,C.A. 34260,10.5,F33,S
+300,1,1,"Baxter, Mrs. James (Helene DeLaudeniere Chaput)",female,50.0,0,1,PC 17558,247.5208,B58 B60,C
+455,0,3,"Peduzzi, Mr. Joseph",male,,0,0,A/5 2817,8.05,,S
+60,0,3,"Goodwin, Master. William Frederick",male,11.0,5,2,CA 2144,46.9,,S
+880,1,1,"Potter, Mrs. Thomas Jr (Lily Alexenia Wilson)",female,56.0,0,1,11767,83.1583,C50,C
+43,0,3,"Kraeff, Mr. Theodor",male,,0,0,349253,7.8958,,C
+500,0,3,"Svensson, Mr. Olof",male,24.0,0,0,350035,7.7958,,S
+236,0,3,"Harknett, Miss. Alice Phoebe",female,,0,0,W./C. 6609,7.55,,S
+255,0,3,"Rosblom, Mrs. Viktor (Helena Wilhelmina)",female,41.0,0,2,370129,20.2125,,S
+346,1,2,"Brown, Miss. Amelia ""Mildred""",female,24.0,0,0,248733,13.0,F33,S
+105,0,3,"Gustafsson, Mr. Anders Vilhelm",male,37.0,2,0,3101276,7.925,,S
+316,1,3,"Nilsson, Miss. Helmina Josefina",female,26.0,0,0,347470,7.8542,,S
+873,0,1,"Carlsson, Mr. Frans Olof",male,33.0,0,0,695,5.0,B51 B53 B55,S
+4,1,1,"Futrelle, Mrs. Jacques Heath (Lily May Peel)",female,35.0,1,0,113803,53.1,C123,S
+805,1,3,"Hedman, Mr. Oskar Arvid",male,27.0,0,0,347089,6.975,,S
+225,1,1,"Hoyt, Mr. Frederick Maxfield",male,38.0,1,0,19943,90.0,C93,S
+772,0,3,"Jensen, Mr. Niels Peder",male,48.0,0,0,350047,7.8542,,S
+539,0,3,"Risien, Mr. Samuel Beard",male,,0,0,364498,14.5,,S
+249,1,1,"Beckwith, Mr. Richard Leonard",male,37.0,1,1,11751,52.5542,D35,S
+32,1,1,"Spencer, Mrs. William Augustus (Marie Eugenie)",female,,1,0,PC 17569,146.5208,B78,C
+268,1,3,"Persson, Mr. Ernst Ulrik",male,25.0,1,0,347083,7.775,,S
+544,1,2,"Beane, Mr. Edward",male,32.0,1,0,2908,26.0,,S
+685,0,2,"Brown, Mr. Thomas William Solomon",male,60.0,1,1,29750,39.0,,S
+608,1,1,"Daniel, Mr. Robert Williams",male,27.0,0,0,113804,30.5,,S
+749,0,1,"Marvin, Mr. Daniel Warner",male,19.0,1,0,113773,53.1,D30,S
+234,1,3,"Asplund, Miss. Lillian Gertrud",female,5.0,4,2,347077,31.3875,,S
+641,0,3,"Jensen, Mr. Hans Peder",male,20.0,0,0,350050,7.8542,,S
+707,1,2,"Kelly, Mrs. Florence ""Fannie""",female,45.0,0,0,223596,13.5,,S
+611,0,3,"Andersson, Mrs. Anders Johan (Alfrida Konstantia Brogren)",female,39.0,1,5,347082,31.275,,S
+647,0,3,"Cor, Mr. Liudevit",male,19.0,0,0,349231,7.8958,,S
+148,0,3,"Ford, Miss. Robina Maggie ""Ruby""",female,9.0,2,2,W./C. 6608,34.375,,S
+574,1,3,"Kelly, Miss. Mary",female,,0,0,14312,7.75,,Q
+809,0,2,"Meyer, Mr. August",male,39.0,0,0,248723,13.0,,S
+535,0,3,"Cacic, Miss. Marija",female,30.0,0,0,315084,8.6625,,S
+588,1,1,"Frolicher-Stehli, Mr. Maxmillian",male,60.0,1,1,13567,79.2,B41,C
+331,1,3,"McCoy, Miss. Agnes",female,,2,0,367226,23.25,,Q
+569,0,3,"Doharr, Mr. Tannous",male,,0,0,2686,7.2292,,C
+725,1,1,"Chambers, Mr. Norman Campbell",male,27.0,1,0,113806,53.1,E8,S
+100,0,2,"Kantor, Mr. Sinai",male,34.0,1,0,244367,26.0,,S
+708,1,1,"Calderhead, Mr. Edward Pennington",male,42.0,0,0,PC 17476,26.2875,E24,S
+277,0,3,"Lindblom, Miss. Augusta Charlotta",female,45.0,0,0,347073,7.75,,S
+418,1,2,"Silven, Miss. Lyyli Karoliina",female,18.0,0,2,250652,13.0,,S
+463,0,1,"Gee, Mr. Arthur H",male,47.0,0,0,111320,38.5,E63,S
+665,1,3,"Lindqvist, Mr. Eino William",male,20.0,1,0,STON/O 2. 3101285,7.925,,S
+718,1,2,"Troutt, Miss. Edwina Celia ""Winnie""",female,27.0,0,0,34218,10.5,E101,S
+850,1,1,"Goldenberg, Mrs. Samuel L (Edwiga Grabowska)",female,,1,0,17453,89.1042,C92,C
+516,0,1,"Walker, Mr. William Anderson",male,47.0,0,0,36967,34.0208,D46,S
+633,1,1,"Stahelin-Maeglin, Dr. Max",male,32.0,0,0,13214,30.5,B50,C
+538,1,1,"LeRoy, Miss. Bertha",female,30.0,0,0,PC 17761,106.425,,C
+151,0,2,"Bateman, Rev. Robert James",male,51.0,0,0,S.O.P. 1166,12.525,,S
+79,1,2,"Caldwell, Master. Alden Gates",male,0.83,0,2,248738,29.0,,S
+10,1,2,"Nasser, Mrs. Nicholas (Adele Achem)",female,14.0,1,0,237736,30.0708,,C
+143,1,3,"Hakkarainen, Mrs. Pekka Pietari (Elin Matilda Dolck)",female,24.0,1,0,STON/O2. 3101279,15.85,,S
+76,0,3,"Moen, Mr. Sigurd Hansen",male,25.0,0,0,348123,7.65,F G73,S
+254,0,3,"Lobb, Mr. William Arthur",male,30.0,1,0,A/5. 3336,16.1,,S
+30,0,3,"Todoroff, Mr. Lalio",male,,0,0,349216,7.8958,,S
+170,0,3,"Ling, Mr. Lee",male,28.0,0,0,1601,56.4958,,S
+747,0,3,"Abbott, Mr. Rossmore Edward",male,16.0,1,1,C.A. 2673,20.25,,S
+212,1,2,"Cameron, Miss. Clear Annie",female,35.0,0,0,F.C.C. 13528,21.0,,S
+636,1,2,"Davis, Miss. Mary",female,28.0,0,0,237668,13.0,,S
+689,0,3,"Fischer, Mr. Eberhard Thelander",male,18.0,0,0,350036,7.7958,,S
+600,1,1,"Duff Gordon, Sir. Cosmo Edmund (""Mr Morgan"")",male,49.0,1,0,PC 17485,56.9292,A20,C
+423,0,3,"Zimmerman, Mr. Leo",male,29.0,0,0,315082,7.875,,S
+59,1,2,"West, Miss. Constance Mirium",female,5.0,1,2,C.A. 34651,27.75,,S
+504,0,3,"Laitinen, Miss. Kristina Sofia",female,37.0,0,0,4135,9.5875,,S
+352,0,1,"Williams-Lambert, Mr. Fletcher Fellows",male,,0,0,113510,35.0,C128,S
+542,0,3,"Andersson, Miss. Ingeborg Constanzia",female,9.0,4,2,347082,31.275,,S
+89,1,1,"Fortune, Miss. Mabel Helen",female,23.0,3,2,19950,263.0,C23 C25 C27,S
+433,1,2,"Louch, Mrs. Charles Alexander (Alice Adelaide Slow)",female,42.0,1,0,SC/AH 3085,26.0,,S
+566,0,3,"Davies, Mr. Alfred J",male,24.0,2,0,A/4 48871,24.15,,S
+502,0,3,"Canavan, Miss. Mary",female,21.0,0,0,364846,7.75,,Q
+128,1,3,"Madsen, Mr. Fridtjof Arne",male,24.0,0,0,C 17369,7.1417,,S
+688,0,3,"Dakic, Mr. Branko",male,19.0,0,0,349228,10.1708,,S
+329,1,3,"Goldsmith, Mrs. Frank John (Emily Alice Brown)",female,31.0,1,1,363291,20.525,,S
+845,0,3,"Culumovic, Mr. Jeso",male,17.0,0,0,315090,8.6625,,S
+886,0,3,"Rice, Mrs. William (Margaret Norton)",female,39.0,0,5,382652,29.125,,Q
+581,1,2,"Christy, Miss. Julie Rachel",female,25.0,1,1,237789,30.0,,S
+568,0,3,"Palsson, Mrs. Nils (Alma Cornelia Berglund)",female,29.0,0,4,349909,21.075,,S
+152,1,1,"Pears, Mrs. Thomas (Edith Wearne)",female,22.0,1,0,113776,66.6,C2,S
+342,1,1,"Fortune, Miss. Alice Elizabeth",female,24.0,3,2,19950,263.0,C23 C25 C27,S
+272,1,3,"Tornquist, Mr. William Henry",male,25.0,0,0,LINE,0.0,,S
+737,0,3,"Ford, Mrs. Edward (Margaret Ann Watson)",female,48.0,1,3,W./C. 6608,34.375,,S
+700,0,3,"Humblen, Mr. Adolf Mathias Nicolai Olsen",male,42.0,0,0,348121,7.65,F G63,S
+291,1,1,"Barber, Miss. Ellen ""Nellie""",female,26.0,0,0,19877,78.85,,S
+141,0,3,"Boulos, Mrs. Joseph (Sultana)",female,,0,2,2678,15.2458,,C
+261,0,3,"Smith, Mr. Thomas",male,,0,0,384461,7.75,,Q
+163,0,3,"Bengtsson, Mr. John Viktor",male,26.0,0,0,347068,7.775,,S
+232,0,3,"Larsson, Mr. Bengt Edvin",male,29.0,0,0,347067,7.775,,S
+802,1,2,"Collyer, Mrs. Harvey (Charlotte Annie Tate)",female,31.0,1,1,C.A. 31921,26.25,,S
+844,0,3,"Lemberopolous, Mr. Peter L",male,34.5,0,0,2683,6.4375,,C
+691,1,1,"Dick, Mr. Albert Adrian",male,31.0,1,0,17474,57.0,B20,S
+649,0,3,"Willey, Mr. Edward",male,,0,0,S.O./P.P. 751,7.55,,S
+137,1,1,"Newsom, Miss. Helen Monypeny",female,19.0,0,2,11752,26.2833,D47,S
+570,1,3,"Jonsson, Mr. Carl",male,32.0,0,0,350417,7.8542,,S
+862,0,2,"Giles, Mr. Frederick Edward",male,21.0,1,0,28134,11.5,,S
+445,1,3,"Johannesen-Bratthammer, Mr. Bernt",male,,0,0,65306,8.1125,,S
+697,0,3,"Kelly, Mr. James",male,44.0,0,0,363592,8.05,,S
+674,1,2,"Wilhelms, Mr. Charles",male,31.0,0,0,244270,13.0,,S
+748,1,2,"Sinkkonen, Miss. Anna",female,30.0,0,0,250648,13.0,,S
+367,1,1,"Warren, Mrs. Frank Manley (Anna Sophia Atkinson)",female,60.0,1,0,110813,75.25,D37,C
+626,0,1,"Sutton, Mr. Frederick",male,61.0,0,0,36963,32.3208,D50,S
+741,1,1,"Hawksford, Mr. Walter James",male,,0,0,16988,30.0,D45,S
+821,1,1,"Hays, Mrs. Charles Melville (Clara Jennings Gregg)",female,52.0,1,1,12749,93.5,B69,S
+282,0,3,"Olsson, Mr. Nils Johan Goransson",male,28.0,0,0,347464,7.8542,,S
+546,0,1,"Nicholson, Mr. Arthur Ernest",male,64.0,0,0,693,26.0,,S
+237,0,2,"Hold, Mr. Stephen",male,44.0,1,0,26707,26.0,,S
+16,1,2,"Hewlett, Mrs. (Mary D Kingcome) ",female,55.0,0,0,248706,16.0,,S
+565,0,3,"Meanwell, Miss. (Marion Ogden)",female,,0,0,SOTON/O.Q. 392087,8.05,,S
+798,1,3,"Osman, Mrs. Mara",female,31.0,0,0,349244,8.6833,,S
+740,0,3,"Nankoff, Mr. Minko",male,,0,0,349218,7.8958,,S
+549,0,3,"Goldsmith, Mr. Frank John",male,33.0,1,1,363291,20.525,,S
+663,0,1,"Colley, Mr. Edward Pomeroy",male,47.0,0,0,5727,25.5875,E58,S
+482,0,2,"Frost, Mr. Anthony Wood ""Archie""",male,,0,0,239854,0.0,,S
+113,0,3,"Barton, Mr. David John",male,22.0,0,0,324669,8.05,,S
+458,1,1,"Kenyon, Mrs. Frederick R (Marion)",female,,1,0,17464,51.8625,D21,S
+842,0,2,"Mudd, Mr. Thomas Charles",male,16.0,0,0,S.O./P.P. 3,10.5,,S
+518,0,3,"Ryan, Mr. Patrick",male,,0,0,371110,24.15,,Q
+553,0,3,"O'Brien, Mr. Timothy",male,,0,0,330979,7.8292,,Q
+388,1,2,"Buss, Miss. Kate",female,36.0,0,0,27849,13.0,,S
+514,1,1,"Rothschild, Mrs. Martin (Elizabeth L. Barrett)",female,54.0,1,0,PC 17603,59.4,,C
+560,1,3,"de Messemaeker, Mrs. Guillaume Joseph (Emma)",female,36.0,1,0,345572,17.4,,S
+701,1,1,"Astor, Mrs. John Jacob (Madeleine Talmadge Force)",female,18.0,1,0,PC 17757,227.525,C62 C64,C
+241,0,3,"Zabour, Miss. Thamine",female,,1,0,2665,14.4542,,C
+428,1,2,"Phillips, Miss. Kate Florence (""Mrs Kate Louise Phillips Marshall"")",female,19.0,0,0,250655,26.0,,S
+593,0,3,"Elsbury, Mr. William James",male,47.0,0,0,A/5 3902,7.25,,S
+116,0,3,"Pekoniemi, Mr. Edvard",male,21.0,0,0,STON/O 2. 3101294,7.925,,S
+686,0,2,"Laroche, Mr. Joseph Philippe Lemercier",male,25.0,1,2,SC/Paris 2123,41.5792,,C
+155,0,3,"Olsen, Mr. Ole Martin",male,,0,0,Fa 265302,7.3125,,S
+308,1,1,"Penasco y Castellana, Mrs. Victor de Satode (Maria Josefa Perez de Soto y Vallejo)",female,17.0,1,0,PC 17758,108.9,C65,C
+765,0,3,"Eklund, Mr. Hans Linus",male,16.0,0,0,347074,7.775,,S
+597,1,2,"Leitch, Miss. Jessie Wills",female,,0,0,248727,33.0,,S
+242,1,3,"Murphy, Miss. Katherine ""Kate""",female,,1,0,367230,15.5,,Q
+823,0,1,"Reuchlin, Jonkheer. John George",male,38.0,0,0,19972,0.0,,S
+380,0,3,"Gustafsson, Mr. Karl Gideon",male,19.0,0,0,347069,7.775,,S
+336,0,3,"Denkoff, Mr. Mitto",male,,0,0,349225,7.8958,,S
+488,0,1,"Kent, Mr. Edward Austin",male,58.0,0,0,11771,29.7,B37,C
+672,0,1,"Davidson, Mr. Thornton",male,31.0,1,0,F.C. 12750,52.0,B71,S
+791,0,3,"Keane, Mr. Andrew ""Andy""",male,,0,0,12460,7.75,,Q
+340,0,1,"Blackwell, Mr. Stephen Weart",male,45.0,0,0,113784,35.5,T,S
+879,0,3,"Laleff, Mr. Kristo",male,,0,0,349217,7.8958,,S
+464,0,2,"Milling, Mr. Jacob Christian",male,48.0,0,0,234360,13.0,,S
+717,1,1,"Endres, Miss. Caroline Louise",female,38.0,0,0,PC 17757,227.525,C45,C
+343,0,2,"Collander, Mr. Erik Gustaf",male,28.0,0,0,248740,13.0,,S
+276,1,1,"Andrews, Miss. Kornelia Theodosia",female,63.0,1,0,13502,77.9583,D7,S
+530,0,2,"Hocking, Mr. Richard George",male,23.0,2,1,29104,11.5,,S
+861,0,3,"Hansen, Mr. Claus Peter",male,41.0,2,0,350026,14.1083,,S
+8,0,3,"Palsson, Master. Gosta Leonard",male,2.0,3,1,349909,21.075,,S
+841,0,3,"Alhomaki, Mr. Ilmari Rudolf",male,20.0,0,0,SOTON/O2 3101287,7.925,,S
+231,1,1,"Harris, Mrs. Henry Birkhardt (Irene Wallach)",female,35.0,1,0,36973,83.475,C83,S
+338,1,1,"Burns, Miss. Elizabeth Margaret",female,41.0,0,0,16966,134.5,E40,C
+286,0,3,"Stankovic, Mr. Ivan",male,33.0,0,0,349239,8.6625,,C
+381,1,1,"Bidois, Miss. Rosalie",female,42.0,0,0,PC 17757,227.525,,C
+468,0,1,"Smart, Mr. John Montgomery",male,56.0,0,0,113792,26.55,,S
+838,0,3,"Sirota, Mr. Maurice",male,,0,0,392092,8.05,,S
+742,0,1,"Cavendish, Mr. Tyrell William",male,36.0,1,0,19877,78.85,C46,S
+617,0,3,"Danbom, Mr. Ernst Gilbert",male,34.0,1,1,347080,14.4,,S
+485,1,1,"Bishop, Mr. Dickinson H",male,25.0,1,0,11967,91.0792,B49,C
+437,0,3,"Ford, Miss. Doolina Margaret ""Daisy""",female,21.0,2,2,W./C. 6608,34.375,,S
+885,0,3,"Sutehall, Mr. Henry Jr",male,25.0,0,0,SOTON/OQ 392076,7.05,,S
+28,0,1,"Fortune, Mr. Charles Alexander",male,19.0,3,2,19950,263.0,C23 C25 C27,S
+751,1,2,"Wells, Miss. Joan",female,4.0,1,1,29103,23.0,,S
+97,0,1,"Goldschmidt, Mr. George B",male,71.0,0,0,PC 17754,34.6542,A5,C
+6,0,3,"Moran, Mr. James",male,,0,0,330877,8.4583,,Q
+271,0,1,"Cairns, Mr. Alexander",male,,0,0,113798,31.0,,S
+301,1,3,"Kelly, Miss. Anna Katherine ""Annie Kate""",female,,0,0,9234,7.75,,Q
+366,0,3,"Adahl, Mr. Mauritz Nils Martin",male,30.0,0,0,C 7076,7.25,,S
+200,0,2,"Yrois, Miss. Henriette (""Mrs Harbeck"")",female,24.0,0,0,248747,13.0,,S
+776,0,3,"Myhrman, Mr. Pehr Fabian Oliver Malkolm",male,18.0,0,0,347078,7.75,,S
+178,0,1,"Isham, Miss. Ann Elizabeth",female,50.0,0,0,PC 17595,28.7125,C49,C
+728,1,3,"Mannion, Miss. Margareth",female,,0,0,36866,7.7375,,Q
+167,1,1,"Chibnall, Mrs. (Edith Martha Bowerman)",female,,0,1,113505,55.0,E33,S
+869,0,3,"van Melkebeke, Mr. Philemon",male,,0,0,345777,9.5,,S
+313,0,2,"Lahtinen, Mrs. William (Anna Sylfven)",female,26.0,1,1,250651,26.0,,S
+285,0,1,"Smith, Mr. Richard William",male,,0,0,113056,26.0,A19,S
+495,0,3,"Stanley, Mr. Edward Roland",male,21.0,0,0,A/4 45380,8.05,,S
+33,1,3,"Glynn, Miss. Mary Agatha",female,,0,0,335677,7.75,,Q
+417,1,2,"Drew, Mrs. James Vivian (Lulu Thorne Christian)",female,34.0,1,1,28220,32.5,,S
+887,0,2,"Montvila, Rev. Juozas",male,27.0,0,0,211536,13.0,,S
+559,1,1,"Taussig, Mrs. Emil (Tillie Mandelbaum)",female,39.0,1,1,110413,79.65,E67,S
+806,0,3,"Johansson, Mr. Karl Johan",male,31.0,0,0,347063,7.775,,S
+294,0,3,"Haas, Miss. Aloisia",female,24.0,0,0,349236,8.85,,S
+209,1,3,"Carr, Miss. Helen ""Ellen""",female,16.0,0,0,367231,7.75,,Q
+85,1,2,"Ilett, Miss. Bertha",female,17.0,0,0,SO/C 14885,10.5,,S
+38,0,3,"Cann, Mr. Ernest Charles",male,21.0,0,0,A./5. 2152,8.05,,S
+7,0,1,"McCarthy, Mr. Timothy J",male,54.0,0,0,17463,51.8625,E46,S
+426,0,3,"Wiseman, Mr. Phillippe",male,,0,0,A/4. 34244,7.25,,S
+790,0,1,"Guggenheim, Mr. Benjamin",male,46.0,0,0,PC 17593,79.2,B82 B84,C
+389,0,3,"Sadlier, Mr. Matthew",male,,0,0,367655,7.7292,,Q
+258,1,1,"Cherry, Miss. Gladys",female,30.0,0,0,110152,86.5,B77,S
+643,0,3,"Skoog, Miss. Margit Elizabeth",female,2.0,3,2,347088,27.9,,S
+355,0,3,"Yousif, Mr. Wazli",male,,0,0,2647,7.225,,C
+830,1,1,"Stone, Mrs. George Nelson (Martha Evelyn)",female,62.0,0,0,113572,80.0,B28,
+781,1,3,"Ayoub, Miss. Banoura",female,13.0,0,0,2687,7.2292,,C
+267,0,3,"Panula, Mr. Ernesti Arvid",male,16.0,4,1,3101295,39.6875,,S
+506,0,1,"Penasco y Castellana, Mr. Victor de Satode",male,18.0,1,0,PC 17758,108.9,C65,C
+52,0,3,"Nosworthy, Mr. Richard Cater",male,21.0,0,0,A/4. 39886,7.8,,S
+401,1,3,"Niskanen, Mr. Juha",male,39.0,0,0,STON/O 2. 3101289,7.925,,S
+533,0,3,"Elias, Mr. Joseph Jr",male,17.0,1,1,2690,7.2292,,C
+283,0,3,"de Pelsmaeker, Mr. Alfons",male,16.0,0,0,345778,9.5,,S
+442,0,3,"Hampe, Mr. Leon",male,20.0,0,0,345769,9.5,,S
+361,0,3,"Skoog, Mr. Wilhelm",male,40.0,1,4,347088,27.9,,S
+840,1,1,"Marechal, Mr. Pierre",male,,0,0,11774,29.7,C47,C
+509,0,3,"Olsen, Mr. Henry Margido",male,28.0,0,0,C 4001,22.525,,S
+121,0,2,"Hickman, Mr. Stanley George",male,21.0,2,0,S.O.C. 14879,73.5,,S
+320,1,1,"Spedden, Mrs. Frederic Oakley (Margaretta Corning Stone)",female,40.0,1,1,16966,134.5,E34,C
+858,1,1,"Daly, Mr. Peter Denis ",male,51.0,0,0,113055,26.55,E17,S
+501,0,3,"Calic, Mr. Petar",male,17.0,0,0,315086,8.6625,,S
+91,0,3,"Christmann, Mr. Emil",male,29.0,0,0,343276,8.05,,S
+727,1,2,"Renouf, Mrs. Peter Henry (Lillian Jefferys)",female,30.0,3,0,31027,21.0,,S
+671,1,2,"Brown, Mrs. Thomas William Solomon (Elizabeth Catherine Ford)",female,40.0,1,1,29750,39.0,,S
+456,1,3,"Jalsevac, Mr. Ivan",male,29.0,0,0,349240,7.8958,,C
+427,1,2,"Clarke, Mrs. Charles V (Ada Maria Winfield)",female,28.0,1,0,2003,26.0,,S
+63,0,1,"Harris, Mr. Henry Birkhardt",male,45.0,1,0,36973,83.475,C83,S
+51,0,3,"Panula, Master. Juha Niilo",male,7.0,4,1,3101295,39.6875,,S
+454,1,1,"Goldenberg, Mr. Samuel L",male,49.0,1,0,17453,89.1042,C92,C
+394,1,1,"Newell, Miss. Marjorie",female,23.0,1,0,35273,113.275,D36,C
+188,1,1,"Romaine, Mr. Charles Hallace (""Mr C Rolmane"")",male,45.0,0,0,111428,26.55,,S
+368,1,3,"Moussa, Mrs. (Mantoura Boulos)",female,,0,0,2626,7.2292,,C
+759,0,3,"Theobald, Mr. Thomas Leonard",male,34.0,0,0,363294,8.05,,S
+804,1,3,"Thomas, Master. Assad Alexander",male,0.42,0,1,2625,8.5167,,C
+510,1,3,"Lang, Mr. Fang",male,26.0,0,0,1601,56.4958,,S
+788,0,3,"Rice, Master. George Hugh",male,8.0,4,1,382652,29.125,,Q
+298,0,1,"Allison, Miss. Helen Loraine",female,2.0,1,2,113781,151.55,C22 C26,S
+92,0,3,"Andreasson, Mr. Paul Edvin",male,20.0,0,0,347466,7.8542,,S
+754,0,3,"Jonkoff, Mr. Lalio",male,23.0,0,0,349204,7.8958,,S
+547,1,2,"Beane, Mrs. Edward (Ethel Clarke)",female,19.0,1,0,2908,26.0,,S
+492,0,3,"Windelov, Mr. Einar",male,21.0,0,0,SOTON/OQ 3101317,7.25,,S
+2,1,1,"Cumings, Mrs. John Bradley (Florence Briggs Thayer)",female,38.0,1,0,PC 17599,71.2833,C85,C
+777,0,3,"Tobin, Mr. Roger",male,,0,0,383121,7.75,F38,Q
+473,1,2,"West, Mrs. Edwy Arthur (Ada Mary Worth)",female,33.0,1,2,C.A. 34651,27.75,,S
+252,0,3,"Strom, Mrs. Wilhelm (Elna Matilda Persson)",female,29.0,1,1,347054,10.4625,G6,S
+93,0,1,"Chaffee, Mr. Herbert Fuller",male,46.0,1,0,W.E.P. 5734,61.175,E31,S
+635,0,3,"Skoog, Miss. Mabel",female,9.0,3,2,347088,27.9,,S
+44,1,2,"Laroche, Miss. Simonne Marie Anne Andree",female,3.0,1,2,SC/Paris 2123,41.5792,,C
+835,0,3,"Allum, Mr. Owen George",male,18.0,0,0,2223,8.3,,S
+48,1,3,"O'Driscoll, Miss. Bridget",female,,0,0,14311,7.75,,Q
+891,0,3,"Dooley, Mr. Patrick",male,32.0,0,0,370376,7.75,,Q
+264,0,1,"Harrison, Mr. William",male,40.0,0,0,112059,0.0,B94,S
+356,0,3,"Vanden Steen, Mr. Leo Peter",male,28.0,0,0,345783,9.5,,S
+528,0,1,"Farthing, Mr. John",male,,0,0,PC 17483,221.7792,C95,S
+339,1,3,"Dahl, Mr. Karl Edwart",male,45.0,0,0,7598,8.05,,S
+780,1,1,"Robert, Mrs. Edward Scott (Elisabeth Walton McMillan)",female,43.0,0,1,24160,211.3375,B3,S
+21,0,2,"Fynney, Mr. Joseph J",male,35.0,0,0,239865,26.0,,S
+723,0,2,"Gillespie, Mr. William Henry",male,34.0,0,0,12233,13.0,,S
+677,0,3,"Sawyer, Mr. Frederick Charles",male,24.5,0,0,342826,8.05,,S
+349,1,3,"Coutts, Master. William Loch ""William""",male,3.0,1,1,C.A. 37671,15.9,,S
+817,0,3,"Heininen, Miss. Wendla Maria",female,23.0,0,0,STON/O2. 3101290,7.925,,S
+334,0,3,"Vander Planke, Mr. Leo Edmondus",male,16.0,2,0,345764,18.0,,S
+470,1,3,"Baclini, Miss. Helene Barbara",female,0.75,2,1,2666,19.2583,,C
+130,0,3,"Ekstrom, Mr. Johan",male,45.0,0,0,347061,6.975,,S
+191,1,2,"Pinsky, Mrs. (Rosa)",female,32.0,0,0,234604,13.0,,S
+760,1,1,"Rothes, the Countess. of (Lucy Noel Martha Dyer-Edwards)",female,33.0,0,0,110152,86.5,B77,S
+520,0,3,"Pavlovic, Mr. Stefo",male,32.0,0,0,349242,7.8958,,S
+67,1,2,"Nye, Mrs. (Elizabeth Ramell)",female,29.0,0,0,C.A. 29395,10.5,F33,S
+487,1,1,"Hoyt, Mrs. Frederick Maxfield (Jane Anne Forby)",female,35.0,1,0,19943,90.0,C93,S
+19,0,3,"Vander Planke, Mrs. Julius (Emelia Maria Vandemoortele)",female,31.0,1,0,345763,18.0,,S
+702,1,1,"Silverthorne, Mr. Spencer Victor",male,35.0,0,0,PC 17475,26.2875,E24,S
+826,0,3,"Flynn, Mr. John",male,,0,0,368323,6.95,,Q
+333,0,1,"Graham, Mr. George Edward",male,38.0,0,1,PC 17582,153.4625,C91,S
+855,0,2,"Carter, Mrs. Ernest Courtenay (Lilian Hughes)",female,44.0,1,0,244252,26.0,,S
+441,1,2,"Hart, Mrs. Benjamin (Esther Ada Bloomfield)",female,45.0,1,1,F.C.C. 13529,26.25,,S
+775,1,2,"Hocking, Mrs. Elizabeth (Eliza Needs)",female,54.0,1,3,29105,23.0,,S
+675,0,2,"Watson, Mr. Ennis Hastings",male,,0,0,239856,0.0,,S
+552,0,2,"Sharp, Mr. Percival James R",male,27.0,0,0,244358,26.0,,S
+56,1,1,"Woolner, Mr. Hugh",male,,0,0,19947,35.5,C52,S
+653,0,3,"Kalvik, Mr. Johannes Halvorsen",male,21.0,0,0,8475,8.4333,,S
+849,0,2,"Harper, Rev. John",male,28.0,0,1,248727,33.0,,S
+730,0,3,"Ilmakangas, Miss. Pieta Sofia",female,25.0,1,0,STON/O2. 3101271,7.925,,S
+233,0,2,"Sjostedt, Mr. Ernst Adolf",male,59.0,0,0,237442,13.5,,S
+660,0,1,"Newell, Mr. Arthur Webster",male,58.0,0,2,35273,113.275,D48,C
+243,0,2,"Coleridge, Mr. Reginald Charles",male,29.0,0,0,W./C. 14263,10.5,,S
+36,0,1,"Holverson, Mr. Alexander Oskar",male,42.0,1,0,113789,52.0,,S
+541,1,1,"Crosby, Miss. Harriet R",female,36.0,0,2,WE/P 5735,71.0,B22,S
+719,0,3,"McEvoy, Mr. Michael",male,,0,0,36568,15.5,,Q
+752,1,3,"Moor, Master. Meier",male,6.0,0,1,392096,12.475,E121,S
+888,1,1,"Graham, Miss. Margaret Edith",female,19.0,0,0,112053,30.0,B42,S
+122,0,3,"Moore, Mr. Leonard Charles",male,,0,0,A4. 54510,8.05,,S
+411,0,3,"Sdycoff, Mr. Todor",male,,0,0,349222,7.8958,,S
+353,0,3,"Elias, Mr. Tannous",male,15.0,1,1,2695,7.2292,,C
+34,0,2,"Wheadon, Mr. Edward H",male,66.0,0,0,C.A. 24579,10.5,,S
+180,0,3,"Leonard, Mr. Lionel",male,36.0,0,0,LINE,0.0,,S
+646,1,1,"Harper, Mr. Henry Sleeper",male,48.0,1,0,PC 17572,76.7292,D33,C
+819,0,3,"Holm, Mr. John Fredrik Alexander",male,43.0,0,0,C 7075,6.45,,S
+22,1,2,"Beesley, Mr. Lawrence",male,34.0,0,0,248698,13.0,D56,S
+412,0,3,"Hart, Mr. Henry",male,,0,0,394140,6.8583,,Q
+422,0,3,"Charters, Mr. David",male,21.0,0,0,A/5. 13032,7.7333,,Q
+584,0,1,"Ross, Mr. John Hugo",male,36.0,0,0,13049,40.125,A10,C
+729,0,2,"Bryhl, Mr. Kurt Arnold Gottfrid",male,25.0,1,0,236853,26.0,,S
+813,0,2,"Slemen, Mr. Richard James",male,35.0,0,0,28206,10.5,,S
+562,0,3,"Sivic, Mr. Husein",male,40.0,0,0,349251,7.8958,,S
+332,0,1,"Partner, Mr. Austen",male,45.5,0,0,113043,28.5,C124,S
+341,1,2,"Navratil, Master. Edmond Roger",male,2.0,1,1,230080,26.0,F2,S
+247,0,3,"Lindahl, Miss. Agda Thorilda Viktoria",female,25.0,0,0,347071,7.775,,S
+127,0,3,"McMahon, Mr. Martin",male,,0,0,370372,7.75,,Q
+324,1,2,"Caldwell, Mrs. Albert Francis (Sylvia Mae Harbaugh)",female,22.0,1,1,248738,29.0,,S
+398,0,2,"McKane, Mr. Peter David",male,46.0,0,0,28403,26.0,,S
+46,0,3,"Rogers, Mr. William John",male,,0,0,S.C./A.4. 23567,8.05,,S
+65,0,1,"Stewart, Mr. Albert A",male,,0,0,PC 17605,27.7208,,C
+262,1,3,"Asplund, Master. Edvin Rojj Felix",male,3.0,4,2,347077,31.3875,,S
+372,0,3,"Wiklund, Mr. Jakob Alfred",male,18.0,1,0,3101267,6.4958,,S
+376,1,1,"Meyer, Mrs. Edgar Joseph (Leila Saks)",female,,1,0,PC 17604,82.1708,,C
+676,0,3,"Edvardsson, Mr. Gustaf Hjalmar",male,18.0,0,0,349912,7.775,,S
+471,0,3,"Keefe, Mr. Arthur",male,,0,0,323592,7.25,,S
+210,1,1,"Blank, Mr. Henry",male,40.0,0,0,112277,31.0,A31,C
+733,0,2,"Knight, Mr. Robert J",male,,0,0,239855,0.0,,S
+81,0,3,"Waelens, Mr. Achille",male,22.0,0,0,345767,9.0,,S
+609,1,2,"Laroche, Mrs. Joseph (Juliette Marie Louise Lafargue)",female,22.0,1,2,SC/Paris 2123,41.5792,,C
+874,0,3,"Vander Cruyssen, Mr. Victor",male,47.0,0,0,345765,9.0,,S
+435,0,1,"Silvey, Mr. William Baird",male,50.0,1,0,13507,55.9,E44,S
+767,0,1,"Brewe, Dr. Arthur Jackson",male,,0,0,112379,39.6,,C
+768,0,3,"Mangan, Miss. Mary",female,30.5,0,0,364850,7.75,,Q
+168,0,3,"Skoog, Mrs. William (Anna Bernhardina Karlsson)",female,45.0,1,4,347088,27.9,,S
+709,1,1,"Cleaver, Miss. Alice",female,22.0,0,0,113781,151.55,,S
+327,0,3,"Nysveen, Mr. Johan Hansen",male,61.0,0,0,345364,6.2375,,S
+843,1,1,"Serepeca, Miss. Augusta",female,30.0,0,0,113798,31.0,,C
+211,0,3,"Ali, Mr. Ahmed",male,24.0,0,0,SOTON/O.Q. 3101311,7.05,,S
+159,0,3,"Smiljanic, Mr. Mile",male,,0,0,315037,8.6625,,S
+378,0,1,"Widener, Mr. Harry Elkins",male,27.0,0,2,113503,211.5,C82,C
+778,1,3,"Emanuel, Miss. Virginia Ethel",female,5.0,0,0,364516,12.475,,S
+457,0,1,"Millet, Mr. Francis Davis",male,65.0,0,0,13509,26.55,E38,S
+769,0,3,"Moran, Mr. Daniel J",male,,1,0,371110,24.15,,Q
+362,0,2,"del Carlo, Mr. Sebastiano",male,29.0,1,0,SC/PARIS 2167,27.7208,,C
+655,0,3,"Hegarty, Miss. Hanora ""Nora""",female,18.0,0,0,365226,6.75,,Q
+698,1,3,"Mullens, Miss. Katherine ""Katie""",female,,0,0,35852,7.7333,,Q
+444,1,2,"Reynaldo, Ms. Encarnacion",female,28.0,0,0,230434,13.0,,S
+203,0,3,"Johanson, Mr. Jakob Alfred",male,34.0,0,0,3101264,6.4958,,S
+606,0,3,"Lindell, Mr. Edvard Bengtsson",male,36.0,1,0,349910,15.55,,S
+673,0,2,"Mitchell, Mr. Henry Michael",male,70.0,0,0,C.A. 24580,10.5,,S
+846,0,3,"Abbing, Mr. Anthony",male,42.0,0,0,C.A. 5547,7.55,,S
+374,0,1,"Ringhini, Mr. Sante",male,22.0,0,0,PC 17760,135.6333,,C
+667,0,2,"Butler, Mr. Reginald Fenton",male,25.0,0,0,234686,13.0,,S
+61,0,3,"Sirayanian, Mr. Orsen",male,22.0,0,0,2669,7.2292,,C
+642,1,1,"Sagesser, Mlle. Emma",female,24.0,0,0,PC 17477,69.3,B35,C
+469,0,3,"Scanlan, Mr. James",male,,0,0,36209,7.725,,Q
+792,0,2,"Gaskell, Mr. Alfred",male,16.0,0,0,239865,26.0,,S
+465,0,3,"Maisner, Mr. Simon",male,,0,0,A/S 2816,8.05,,S
+551,1,1,"Thayer, Mr. John Borland Jr",male,17.0,0,2,17421,110.8833,C70,C
+523,0,3,"Lahoud, Mr. Sarkis",male,,0,0,2624,7.225,,C
+369,1,3,"Jermyn, Miss. Annie",female,,0,0,14313,7.75,,Q
+864,0,3,"Sage, Miss. Dorothy Edith ""Dolly""",female,,8,2,CA. 2343,69.55,,S
+839,1,3,"Chip, Mr. Chang",male,32.0,0,0,1601,56.4958,,S
+590,0,3,"Murdlin, Mr. Joseph",male,,0,0,A./5. 3235,8.05,,S
+9,1,3,"Johnson, Mrs. Oscar W (Elisabeth Vilhelmina Berg)",female,27.0,0,2,347742,11.1333,,S
+505,1,1,"Maioni, Miss. Roberta",female,16.0,0,0,110152,86.5,B79,S
+572,1,1,"Appleton, Mrs. Edward Dale (Charlotte Lamson)",female,53.0,2,0,11769,51.4792,C101,S
+235,0,2,"Leyson, Mr. Robert William Norman",male,24.0,0,0,C.A. 29566,10.5,,S
+345,0,2,"Fox, Mr. Stanley Hubert",male,36.0,0,0,229236,13.0,,S
+714,0,3,"Larsson, Mr. August Viktor",male,29.0,0,0,7545,9.4833,,S
+477,0,2,"Renouf, Mr. Peter Henry",male,34.0,1,0,31027,21.0,,S
+587,0,2,"Jarvis, Mr. John Denzil",male,47.0,0,0,237565,15.0,,S
+630,0,3,"O'Connell, Mr. Patrick D",male,,0,0,334912,7.7333,,Q
+133,0,3,"Robins, Mrs. Alexander A (Grace Charity Laury)",female,47.0,1,0,A/5. 3337,14.5,,S
+27,0,3,"Emir, Mr. Farred Chehab",male,,0,0,2631,7.225,,C
+612,0,3,"Jardin, Mr. Jose Neto",male,,0,0,SOTON/O.Q. 3101305,7.05,,S
+292,1,1,"Bishop, Mrs. Dickinson H (Helen Walton)",female,19.0,1,0,11967,91.0792,B49,C
+293,0,2,"Levy, Mr. Rene Jacques",male,36.0,0,0,SC/Paris 2163,12.875,D,C
+40,1,3,"Nicola-Yarred, Miss. Jamila",female,14.0,1,0,2651,11.2417,,C
+205,1,3,"Cohen, Mr. Gurshon ""Gus""",male,18.0,0,0,A/5 3540,8.05,,S
+832,1,2,"Richards, Master. George Sibley",male,0.83,1,1,29106,18.75,,S
+716,0,3,"Soholt, Mr. Peter Andreas Lauritz Andersen",male,19.0,0,0,348124,7.65,F G73,S
+596,0,3,"Van Impe, Mr. Jean Baptiste",male,36.0,1,1,345773,24.15,,S
+344,0,2,"Sedgwick, Mr. Charles Frederick Waddington",male,25.0,0,0,244361,13.0,,S
+687,0,3,"Panula, Mr. Jaako Arnold",male,14.0,4,1,3101295,39.6875,,S
+662,0,3,"Badt, Mr. Mohamed",male,40.0,0,0,2623,7.225,,C
+66,1,3,"Moubarek, Master. Gerios",male,,1,1,2661,15.2458,,C
+820,0,3,"Skoog, Master. Karl Thorsten",male,10.0,3,2,347088,27.9,,S
+865,0,2,"Gill, Mr. John William",male,24.0,0,0,233866,13.0,,S
+323,1,2,"Slayter, Miss. Hilda Mary",female,30.0,0,0,234818,12.35,,Q
+358,0,2,"Funk, Miss. Annie Clemmer",female,38.0,0,0,237671,13.0,,S
+129,1,3,"Peter, Miss. Anna",female,,1,1,2668,22.3583,F E69,C
+166,1,3,"Goldsmith, Master. Frank John William ""Frankie""",male,9.0,0,2,363291,20.525,,S
+799,0,3,"Ibrahim Shawah, Mr. Yousseff",male,30.0,0,0,2685,7.2292,,C
+770,0,3,"Gronnestad, Mr. Daniel Danielsen",male,32.0,0,0,8471,8.3625,,S
+785,0,3,"Ali, Mr. William",male,25.0,0,0,SOTON/O.Q. 3101312,7.05,,S
+399,0,2,"Pain, Dr. Alfred",male,23.0,0,0,244278,10.5,,S
+746,0,1,"Crosby, Capt. Edward Gifford",male,70.0,1,1,WE/P 5735,71.0,B22,S
+498,0,3,"Shellard, Mr. Frederick William",male,,0,0,C.A. 6212,15.1,,S
+297,0,3,"Hanna, Mr. Mansour",male,23.5,0,0,2693,7.2292,,C
+295,0,3,"Mineff, Mr. Ivan",male,24.0,0,0,349233,7.8958,,S
+545,0,1,"Douglas, Mr. Walter Donald",male,50.0,1,0,PC 17761,106.425,C86,C
+755,1,2,"Herman, Mrs. Samuel (Jane Laver)",female,48.0,1,2,220845,65.0,,S
+305,0,3,"Williams, Mr. Howard Hugh ""Harry""",male,,0,0,A/5 2466,8.05,,S
+682,1,1,"Hassab, Mr. Hammad",male,27.0,0,0,PC 17572,76.7292,D49,C
+124,1,2,"Webber, Miss. Susan",female,32.5,0,0,27267,13.0,E101,S
+499,0,1,"Allison, Mrs. Hudson J C (Bessie Waldo Daniels)",female,25.0,1,2,113781,151.55,C22 C26,S
+870,1,3,"Johnson, Master. Harold Theodor",male,4.0,1,1,347742,11.1333,,S
+72,0,3,"Goodwin, Miss. Lillian Amy",female,16.0,5,2,CA 2144,46.9,,S
+120,0,3,"Andersson, Miss. Ellis Anna Maria",female,2.0,4,2,347082,31.275,,S
+325,0,3,"Sage, Mr. George John Jr",male,,8,2,CA. 2343,69.55,,S
+383,0,3,"Tikkanen, Mr. Juho",male,32.0,0,0,STON/O 2. 3101293,7.925,,S
+628,1,1,"Longley, Miss. Gretchen Fiske",female,21.0,0,0,13502,77.9583,D9,S
+744,0,3,"McNamee, Mr. Neal",male,24.0,1,0,376566,16.1,,S
+684,0,3,"Goodwin, Mr. Charles Edward",male,14.0,5,2,CA 2144,46.9,,S
+598,0,3,"Johnson, Mr. Alfred",male,49.0,0,0,LINE,0.0,,S
+866,1,2,"Bystrom, Mrs. (Karolina)",female,42.0,0,0,236852,13.0,,S
+53,1,1,"Harper, Mrs. Henry Sleeper (Myna Haxtun)",female,49.0,1,0,PC 17572,76.7292,D33,C
+732,0,3,"Hassan, Mr. Houssein G N",male,11.0,0,0,2699,18.7875,,C
+306,1,1,"Allison, Master. Hudson Trevor",male,0.92,1,2,113781,151.55,C22 C26,S
+140,0,1,"Giglio, Mr. Victor",male,24.0,0,0,PC 17593,79.2,B86,C
+814,0,3,"Andersson, Miss. Ebba Iris Alfrida",female,6.0,4,2,347082,31.275,,S
+310,1,1,"Francatelli, Miss. Laura Mabel",female,30.0,0,0,PC 17485,56.9292,E36,C
+71,0,2,"Jenkin, Mr. Stephen Curnow",male,32.0,0,0,C.A. 33111,10.5,,S
+529,0,3,"Salonen, Mr. Johan Werner",male,39.0,0,0,3101296,7.925,,S
+466,0,3,"Goncalves, Mr. Manuel Estanslas",male,38.0,0,0,SOTON/O.Q. 3101306,7.05,,S
+319,1,1,"Wick, Miss. Mary Natalie",female,31.0,0,2,36928,164.8667,C7,S
+259,1,1,"Ward, Miss. Anna",female,35.0,0,0,PC 17755,512.3292,,C
+114,0,3,"Jussila, Miss. Katriina",female,20.0,1,0,4136,9.825,,S
+625,0,3,"Bowen, Mr. David John ""Dai""",male,21.0,0,0,54636,16.1,,S
+555,1,3,"Ohman, Miss. Velin",female,22.0,0,0,347085,7.775,,S
+357,1,1,"Bowerman, Miss. Elsie Edith",female,22.0,0,1,113505,55.0,E33,S
+837,0,3,"Pasic, Mr. Jakob",male,21.0,0,0,315097,8.6625,,S
+84,0,1,"Carrau, Mr. Francisco M",male,28.0,0,0,113059,47.1,,S
+184,1,2,"Becker, Master. Richard F",male,1.0,2,1,230136,39.0,F4,S
+183,0,3,"Asplund, Master. Clarence Gustaf Hugo",male,9.0,4,2,347077,31.3875,,S
+145,0,2,"Andrew, Mr. Edgardo Samuel",male,18.0,0,0,231945,11.5,,S
+859,1,3,"Baclini, Mrs. Solomon (Latifa Qurban)",female,24.0,0,3,2666,19.2583,,C
+299,1,1,"Saalfeld, Mr. Adolphe",male,,0,0,19988,30.5,C106,S
+658,0,3,"Bourke, Mrs. John (Catherine)",female,32.0,1,1,364849,15.5,,Q
+507,1,2,"Quick, Mrs. Frederick Charles (Jane Richards)",female,33.0,0,2,26360,26.0,,S
+692,1,3,"Karun, Miss. Manca",female,4.0,0,1,349256,13.4167,,C
+88,0,3,"Slocovski, Mr. Selman Francis",male,,0,0,SOTON/OQ 392086,8.05,,S
+314,0,3,"Hendekovic, Mr. Ignjac",male,28.0,0,0,349243,7.8958,,S
+800,0,3,"Van Impe, Mrs. Jean Baptiste (Rosalie Paula Govaert)",female,30.0,1,1,345773,24.15,,S
+614,0,3,"Horgan, Mr. John",male,,0,0,370377,7.75,,Q
+12,1,1,"Bonnell, Miss. Elizabeth",female,58.0,0,0,113783,26.55,C103,S
+771,0,3,"Lievens, Mr. Rene Aime",male,24.0,0,0,345781,9.5,,S
+365,0,3,"O'Brien, Mr. Thomas",male,,1,0,370365,15.5,,Q
+876,1,3,"Najib, Miss. Adele Kiamie ""Jane""",female,15.0,0,0,2667,7.225,,C
+195,1,1,"Brown, Mrs. James Joseph (Margaret Tobin)",female,44.0,0,0,PC 17610,27.7208,B4,C
+594,0,3,"Bourke, Miss. Mary",female,,0,2,364848,7.75,,Q
+654,1,3,"O'Leary, Miss. Hanora ""Norah""",female,,0,0,330919,7.8292,,Q
+402,0,3,"Adams, Mr. John",male,26.0,0,0,341826,8.05,,S
+83,1,3,"McDermott, Miss. Brigdet Delia",female,,0,0,330932,7.7875,,Q
+669,0,3,"Cook, Mr. Jacob",male,43.0,0,0,A/5 3536,8.05,,S
+878,0,3,"Petroff, Mr. Nedelio",male,19.0,0,0,349212,7.8958,,S
+833,0,3,"Saad, Mr. Amin",male,,0,0,2671,7.2292,,C
+75,1,3,"Bing, Mr. Lee",male,32.0,0,0,1601,56.4958,,S
+722,0,3,"Jensen, Mr. Svend Lauritz",male,17.0,1,0,350048,7.0542,,S
+251,0,3,"Reed, Mr. James George",male,,0,0,362316,7.25,,S
+238,1,2,"Collyer, Miss. Marjorie ""Lottie""",female,8.0,0,2,C.A. 31921,26.25,,S
+146,0,2,"Nicholls, Mr. Joseph Charles",male,19.0,1,1,C.A. 33112,36.75,,S
+808,0,3,"Pettersson, Miss. Ellen Natalia",female,18.0,0,0,347087,7.775,,S
+131,0,3,"Drazenoic, Mr. Jozef",male,33.0,0,0,349241,7.8958,,C
+576,0,3,"Patchett, Mr. George",male,19.0,0,0,358585,14.5,,S
+515,0,3,"Coleff, Mr. Satio",male,24.0,0,0,349209,7.4958,,S
+847,0,3,"Sage, Mr. Douglas Bullen",male,,8,2,CA. 2343,69.55,,S
+648,1,1,"Simonius-Blumer, Col. Oberst Alfons",male,56.0,0,0,13213,35.5,A26,C
+443,0,3,"Petterson, Mr. Johan Emil",male,25.0,1,0,347076,7.775,,S
+478,0,3,"Braund, Mr. Lewis Richard",male,29.0,1,0,3460,7.0458,,S
+537,0,1,"Butt, Major. Archibald Willingham",male,45.0,0,0,113050,26.55,B38,S
+169,0,1,"Baumann, Mr. John D",male,,0,0,PC 17318,25.925,,S
+149,0,2,"Navratil, Mr. Michel (""Louis M Hoffman"")",male,36.5,0,2,230080,26.0,F2,S
+290,1,3,"Connolly, Miss. Kate",female,22.0,0,0,370373,7.75,,Q
+15,0,3,"Vestrom, Miss. Hulda Amanda Adolfina",female,14.0,0,0,350406,7.8542,,S
+386,0,2,"Davies, Mr. Charles Henry",male,18.0,0,0,S.O.C. 14879,73.5,,S
+811,0,3,"Alexander, Mr. William",male,26.0,0,0,3474,7.8875,,S
+78,0,3,"Moutal, Mr. Rahamin Haim",male,,0,0,374746,8.05,,S
+738,1,1,"Lesurer, Mr. Gustave J",male,35.0,0,0,PC 17755,512.3292,B101,C
+452,0,3,"Hagland, Mr. Ingvald Olai Olsen",male,,1,0,65303,19.9667,,S
+35,0,1,"Meyer, Mr. Edgar Joseph",male,28.0,1,0,PC 17604,82.1708,,C
+347,1,2,"Smith, Miss. Marion Elsie",female,40.0,0,0,31418,13.0,,S
+436,1,1,"Carter, Miss. Lucile Polk",female,14.0,1,2,113760,120.0,B96 B98,S
+390,1,2,"Lehmann, Miss. Bertha",female,17.0,0,0,SC 1748,12.0,,C
+657,0,3,"Radeff, Mr. Alexander",male,,0,0,349223,7.8958,,S
+695,0,1,"Weir, Col. John",male,60.0,0,0,113800,26.55,,S
+586,1,1,"Taussig, Miss. Ruth",female,18.0,0,2,110413,79.65,E68,S
+384,1,1,"Holverson, Mrs. Alexander Oskar (Mary Aline Towner)",female,35.0,1,0,113789,52.0,,S
+58,0,3,"Novel, Mr. Mansouer",male,28.5,0,0,2697,7.2292,,C
+246,0,1,"Minahan, Dr. William Edward",male,44.0,2,0,19928,90.0,C78,Q
+557,1,1,"Duff Gordon, Lady. (Lucille Christiana Sutherland) (""Mrs Morgan"")",female,48.0,1,0,11755,39.6,A16,C
+605,1,1,"Homer, Mr. Harry (""Mr E Haven"")",male,35.0,0,0,111426,26.55,,C
+350,0,3,"Dimic, Mr. Jovan",male,42.0,0,0,315088,8.6625,,S
+659,0,2,"Eitemiller, Mr. George Floyd",male,23.0,0,0,29751,13.0,,S
+415,1,3,"Sundman, Mr. Johan Julian",male,44.0,0,0,STON/O 2. 3101269,7.925,,S
+713,1,1,"Taylor, Mr. Elmer Zebley",male,48.0,1,0,19996,52.0,C126,S
+474,1,2,"Jerwan, Mrs. Amin S (Marie Marthe Thuillard)",female,23.0,0,0,SC/AH Basle 541,13.7917,D,C
+139,0,3,"Osen, Mr. Olaf Elon",male,16.0,0,0,7534,9.2167,,S
+224,0,3,"Nenkoff, Mr. Christo",male,,0,0,349234,7.8958,,S
+221,1,3,"Sunderland, Mr. Victor Francis",male,16.0,0,0,SOTON/OQ 392089,8.05,,S
+68,0,3,"Crease, Mr. Ernest James",male,19.0,0,0,S.P. 3464,8.1583,,S
+622,1,1,"Kimball, Mr. Edwin Nelson Jr",male,42.0,1,0,11753,52.5542,D19,S
+467,0,2,"Campbell, Mr. William",male,,0,0,239853,0.0,,S
+525,0,3,"Kassem, Mr. Fared",male,,0,0,2700,7.2292,,C
+17,0,3,"Rice, Master. Eugene",male,2.0,4,1,382652,29.125,,Q
+430,1,3,"Pickard, Mr. Berk (Berk Trembisky)",male,32.0,0,0,SOTON/O.Q. 392078,8.05,E10,S
+90,0,3,"Celotti, Mr. Francesco",male,24.0,0,0,343275,8.05,,S
+486,0,3,"Lefebre, Miss. Jeannie",female,,3,1,4133,25.4667,,S
+831,1,3,"Yasbeck, Mrs. Antoni (Selini Alexander)",female,15.0,1,0,2659,14.4542,,C
+440,0,2,"Kvillner, Mr. Johan Henrik Johannesson",male,31.0,0,0,C.A. 18723,10.5,,S
+244,0,3,"Maenpaa, Mr. Matti Alexanteri",male,22.0,0,0,STON/O 2. 3101275,7.125,,S
+882,0,3,"Markun, Mr. Johann",male,33.0,0,0,349257,7.8958,,S
+287,1,3,"de Mulder, Mr. Theodore",male,30.0,0,0,345774,9.5,,S
+735,0,2,"Troupiansky, Mr. Moses Aaron",male,23.0,0,0,233639,13.0,,S
+620,0,2,"Gavey, Mr. Lawrence",male,26.0,0,0,31028,10.5,,S
+296,0,1,"Lewy, Mr. Ervin G",male,,0,0,PC 17612,27.7208,,C
+187,1,3,"O'Brien, Mrs. Thomas (Johanna ""Hannah"" Godfrey)",female,,1,0,370365,15.5,,Q
+629,0,3,"Bostandyeff, Mr. Guentcho",male,26.0,0,0,349224,7.8958,,S
+123,0,2,"Nasser, Mr. Nicholas",male,32.5,1,0,237736,30.0708,,C
+678,1,3,"Turja, Miss. Anna Sofia",female,18.0,0,0,4138,9.8417,,S
+263,0,1,"Taussig, Mr. Emil",male,52.0,1,1,110413,79.65,E67,S
+439,0,1,"Fortune, Mr. Mark",male,64.0,1,4,19950,263.0,C23 C25 C27,S
+410,0,3,"Lefebre, Miss. Ida",female,,3,1,4133,25.4667,,S
+497,1,1,"Eustis, Miss. Elizabeth Mussey",female,54.0,1,0,36947,78.2667,D20,C
+522,0,3,"Vovk, Mr. Janko",male,22.0,0,0,349252,7.8958,,S
+766,1,1,"Hogeboom, Mrs. John C (Anna Andrews)",female,51.0,1,0,13502,77.9583,D11,S
+408,1,2,"Richards, Master. William Rowe",male,3.0,1,1,29106,18.75,,S
+420,0,3,"Van Impe, Miss. Catharina",female,10.0,0,2,345773,24.15,,S
+453,0,1,"Foreman, Mr. Benjamin Laventall",male,30.0,0,0,113051,27.75,C111,C
+447,1,2,"Mellinger, Miss. Madeleine Violet",female,13.0,0,1,250644,19.5,,S
+197,0,3,"Mernagh, Mr. Robert",male,,0,0,368703,7.75,,Q
+227,1,2,"Mellors, Mr. William John",male,19.0,0,0,SW/PP 751,10.5,,S
+852,0,3,"Svensson, Mr. Johan",male,74.0,0,0,347060,7.775,,S
+763,1,3,"Barah, Mr. Hanna Assi",male,20.0,0,0,2663,7.2292,,C
+257,1,1,"Thorne, Mrs. Gertrude Maybelle",female,,0,0,PC 17585,79.2,,C
+407,0,3,"Widegren, Mr. Carl/Charles Peter",male,51.0,0,0,347064,7.75,,S
+103,0,1,"White, Mr. Richard Frasar",male,21.0,0,1,35281,77.2875,D26,S
+315,0,2,"Hart, Mr. Benjamin",male,43.0,1,1,F.C.C. 13529,26.25,,S
+77,0,3,"Staneff, Mr. Ivan",male,,0,0,349208,7.8958,,S
+632,0,3,"Lundahl, Mr. Johan Svensson",male,51.0,0,0,347743,7.0542,,S
+750,0,3,"Connaghton, Mr. Michael",male,31.0,0,0,335097,7.75,,Q
+627,0,2,"Kirkland, Rev. Charles Leonard",male,57.0,0,0,219533,12.35,,Q
+96,0,3,"Shorney, Mr. Charles Joseph",male,,0,0,374910,8.05,,S
+171,0,1,"Van der hoef, Mr. Wyckoff",male,61.0,0,0,111240,33.5,B19,S
+881,1,2,"Shelley, Mrs. William (Imanita Parrish Hall)",female,25.0,0,1,230433,26.0,,S
+95,0,3,"Coxon, Mr. Daniel",male,59.0,0,0,364500,7.25,,S
+215,0,3,"Kiernan, Mr. Philip",male,,1,0,367229,7.75,,Q
+39,0,3,"Vander Planke, Miss. Augusta Maria",female,18.0,2,0,345764,18.0,,S
+774,0,3,"Elias, Mr. Dibo",male,,0,0,2674,7.225,,C
+37,1,3,"Mamee, Mr. Hanna",male,,0,0,2677,7.2292,,C
+181,0,3,"Sage, Miss. Constance Gladys",female,,8,2,CA. 2343,69.55,,S
+177,0,3,"Lefebre, Master. Henry Forbes",male,,3,1,4133,25.4667,,S
+812,0,3,"Lester, Mr. James",male,39.0,0,0,A/4 48871,24.15,,S
+496,0,3,"Yousseff, Mr. Gerious",male,,0,0,2627,14.4583,,C
+503,0,3,"O'Sullivan, Miss. Bridget Mary",female,,0,0,330909,7.6292,,Q
+216,1,1,"Newell, Miss. Madeleine",female,31.0,1,0,35273,113.275,D36,C
+395,1,3,"Sandstrom, Mrs. Hjalmar (Agnes Charlotta Bengtsson)",female,24.0,0,2,PP 9549,16.7,G6,S
+720,0,3,"Johnson, Mr. Malkolm Joackim",male,33.0,0,0,347062,7.775,,S
+213,0,3,"Perkin, Mr. John Henry",male,22.0,0,0,A/5 21174,7.25,,S
+644,1,3,"Foo, Mr. Choong",male,,0,0,1601,56.4958,,S
+583,0,2,"Downton, Mr. William James",male,54.0,0,0,28403,26.0,,S
+132,0,3,"Coelho, Mr. Domingos Fernandeo",male,20.0,0,0,SOTON/O.Q. 3101307,7.05,,S
+363,0,3,"Barbara, Mrs. (Catherine David)",female,45.0,0,1,2691,14.4542,,C
+461,1,1,"Anderson, Mr. Harry",male,48.0,0,0,19952,26.55,E12,S
+186,0,1,"Rood, Mr. Hugh Roscoe",male,,0,0,113767,50.0,A32,S
+14,0,3,"Andersson, Mr. Anders Johan",male,39.0,1,5,347082,31.275,,S
+1,0,3,"Braund, Mr. Owen Harris",male,22.0,1,0,A/5 21171,7.25,,S
+694,0,3,"Saad, Mr. Khalil",male,25.0,0,0,2672,7.225,,C
+476,0,1,"Clifford, Mr. George Quincy",male,,0,0,110465,52.0,A14,S
+348,1,3,"Davison, Mrs. Thomas Henry (Mary E Finck)",female,,1,0,386525,16.1,,S
+489,0,3,"Somerton, Mr. Francis William",male,30.0,0,0,A.5. 18509,8.05,,S
+69,1,3,"Andersson, Miss. Erna Alexandra",female,17.0,4,2,3101281,7.925,,S
+883,0,3,"Dahlberg, Miss. Gerda Ulrika",female,22.0,0,0,7552,10.5167,,S
+18,1,2,"Williams, Mr. Charles Eugene",male,,0,0,244373,13.0,,S
+31,0,1,"Uruchurtu, Don. Manuel E",male,40.0,0,0,PC 17601,27.7208,,C
+619,1,2,"Becker, Miss. Marion Louise",female,4.0,2,1,230136,39.0,F4,S
+526,0,3,"Farrell, Mr. James",male,40.5,0,0,367232,7.75,,Q
+585,0,3,"Paulner, Mr. Uscher",male,,0,0,3411,8.7125,,C
+274,0,1,"Natsch, Mr. Charles H",male,37.0,0,1,PC 17596,29.7,C118,C
+715,0,2,"Greenberg, Mr. Samuel",male,52.0,0,0,250647,13.0,,S
+438,1,2,"Richards, Mrs. Sidney (Emily Hocking)",female,24.0,2,3,29106,18.75,,S
+193,1,3,"Andersen-Jensen, Miss. Carla Christine Nielsine",female,19.0,1,0,350046,7.8542,,S
+275,1,3,"Healy, Miss. Hanora ""Nora""",female,,0,0,370375,7.75,,Q
+173,1,3,"Johnson, Miss. Eleanor Ileen",female,1.0,1,1,347742,11.1333,,S
+807,0,1,"Andrews, Mr. Thomas Jr",male,39.0,0,0,112050,0.0,A36,S
+680,1,1,"Cardeza, Mr. Thomas Drake Martinez",male,36.0,0,1,PC 17755,512.3292,B51 B53 B55,C
+304,1,2,"Keane, Miss. Nora A",female,,0,0,226593,12.35,E101,Q
+370,1,1,"Aubart, Mme. Leontine Pauline",female,24.0,0,0,PC 17477,69.3,B35,C
+239,0,2,"Pengelly, Mr. Frederick William",male,19.0,0,0,28665,10.5,,S
+825,0,3,"Panula, Master. Urho Abraham",male,2.0,4,1,3101295,39.6875,,S
+284,1,3,"Dorking, Mr. Edward Arthur",male,19.0,0,0,A/5. 10482,8.05,,S
+182,0,2,"Pernot, Mr. Rene",male,,0,0,SC/PARIS 2131,15.05,,C
+64,0,3,"Skoog, Master. Harald",male,4.0,3,2,347088,27.9,,S
+404,0,3,"Hakkarainen, Mr. Pekka Pietari",male,28.0,1,0,STON/O2. 3101279,15.85,,S
+479,0,3,"Karlsson, Mr. Nils August",male,22.0,0,0,350060,7.5208,,S
+618,0,3,"Lobb, Mrs. William Arthur (Cordelia K Stanlick)",female,26.0,1,0,A/5. 3336,16.1,,S
+3,1,3,"Heikkinen, Miss. Laina",female,26.0,0,0,STON/O2. 3101282,7.925,,S
+337,0,1,"Pears, Mr. Thomas Clinton",male,29.0,1,0,113776,66.6,C2,S
+764,1,1,"Carter, Mrs. William Ernest (Lucile Polk)",female,36.0,1,2,113760,120.0,B96 B98,S
+696,0,2,"Chapman, Mr. Charles Henry",male,52.0,0,0,248731,13.5,,S
+783,0,1,"Long, Mr. Milton Clyde",male,29.0,0,0,113501,30.0,D6,S
+318,0,2,"Moraweck, Dr. Ernest",male,54.0,0,0,29011,14.0,,S
+706,0,2,"Morley, Mr. Henry Samuel (""Mr Henry Marshall"")",male,39.0,0,0,250655,26.0,,S
+432,1,3,"Thorneycroft, Mrs. Percival (Florence Kate White)",female,,1,0,376564,16.1,,S
+50,0,3,"Arnold-Franchi, Mrs. Josef (Josefine Franchi)",female,18.0,1,0,349237,17.8,,S
+136,0,2,"Richard, Mr. Emile",male,23.0,0,0,SC/PARIS 2133,15.0458,,C
+889,0,3,"Johnston, Miss. Catherine Helen ""Carrie""",female,,1,2,W./C. 6607,23.45,,S
+604,0,3,"Torber, Mr. Ernst William",male,44.0,0,0,364511,8.05,,S
+5,0,3,"Allen, Mr. William Henry",male,35.0,0,0,373450,8.05,,S
+613,1,3,"Murphy, Miss. Margaret Jane",female,,1,0,367230,15.5,,Q
+724,0,2,"Hodges, Mr. Henry Price",male,50.0,0,0,250643,13.0,,S
+758,0,2,"Bailey, Mr. Percy Andrew",male,18.0,0,0,29108,11.5,,S
+142,1,3,"Nysten, Miss. Anna Sofia",female,22.0,0,0,347081,7.75,,S
+416,0,3,"Meek, Mrs. Thomas (Annie Louise Rowley)",female,,0,0,343095,8.05,,S
+668,0,3,"Rommetvedt, Mr. Knud Paust",male,,0,0,312993,7.775,,S
+387,0,3,"Goodwin, Master. Sidney Leonard",male,1.0,5,2,CA 2144,46.9,,S
+87,0,3,"Ford, Mr. William Neal",male,16.0,1,3,W./C. 6608,34.375,,S
+94,0,3,"Dean, Mr. Bertram Frank",male,26.0,1,2,C.A. 2315,20.575,,S
+650,1,3,"Stanley, Miss. Amy Zillah Elsie",female,23.0,0,0,CA. 2314,7.55,,S
+508,1,1,"Bradley, Mr. George (""George Arthur Brayton"")",male,,0,0,111427,26.55,,S
+571,1,2,"Harris, Mr. George",male,62.0,0,0,S.W./PP 752,10.5,,S
+317,1,2,"Kantor, Mrs. Sinai (Miriam Sternin)",female,24.0,1,0,244367,26.0,,S
+229,0,2,"Fahlstrom, Mr. Arne Jonas",male,18.0,0,0,236171,13.0,,S
+656,0,2,"Hickman, Mr. Leonard Mark",male,24.0,2,0,S.O.C. 14879,73.5,,S
+281,0,3,"Duane, Mr. Frank",male,65.0,0,0,336439,7.75,,Q
+753,0,3,"Vande Velde, Mr. Johannes Joseph",male,33.0,0,0,345780,9.5,,S
+803,1,1,"Carter, Master. William Thornton II",male,11.0,1,2,113760,120.0,B96 B98,S
+527,1,2,"Ridsdale, Miss. Lucy",female,50.0,0,0,W./C. 14258,10.5,,S
+739,0,3,"Ivanoff, Mr. Kanio",male,,0,0,349201,7.8958,,S
+579,0,3,"Caram, Mrs. Joseph (Maria Elias)",female,,1,0,2689,14.4583,,C
+54,1,2,"Faunthorpe, Mrs. Lizzie (Elizabeth Anne Wilkinson)",female,29.0,1,0,2926,26.0,,S
+867,1,2,"Duran y More, Miss. Asuncion",female,27.0,1,0,SC/PARIS 2149,13.8583,,C
+351,0,3,"Odahl, Mr. Nils Martin",male,23.0,0,0,7267,9.225,,S
+80,1,3,"Dowdell, Miss. Elizabeth",female,30.0,0,0,364516,12.475,,S
+856,1,3,"Aks, Mrs. Sam (Leah Rosen)",female,18.0,0,1,392091,9.35,,S
+872,1,1,"Beckwith, Mrs. Richard Leonard (Sallie Monypeny)",female,47.0,1,1,11751,52.5542,D35,S
+836,1,1,"Compton, Miss. Sara Rebecca",female,39.0,1,1,PC 17756,83.1583,E49,C
+793,0,3,"Sage, Miss. Stella Anna",female,,8,2,CA. 2343,69.55,,S
+521,1,1,"Perreault, Miss. Anne",female,30.0,0,0,12749,93.5,B73,S
diff --git a/tests/data/openai/embedding.json b/tests/data/openai/embedding.json
new file mode 100644
index 000000000..249c78ecf
--- /dev/null
+++ b/tests/data/openai/embedding.json
@@ -0,0 +1 @@
+{"object": "list", "data": [{"object": "embedding", "index": 0, "embedding": [-0.01999368, -0.02016083, 0.013037679, -0.011751912, -0.02810687, 0.0056188027, -0.011726197, -0.01088402, 0.01021542, -0.010967594, 0.0113276085, -0.0106332945, -0.012806241, -0.021626605, -0.00513664, 0.0023031305, 0.021343736, -0.0029026193, 0.009951838, -0.013114825, -0.0057730945, 0.0065799137, -0.016084947, -0.027309695, -0.011906204, 0.0066474164, 0.02921263, -0.013436267, -0.009096803, -0.00037287248, 0.033378515, -0.022912372, -0.036027197, -0.0077338894, -0.02307952, -0.011784056, -0.018527905, -0.0094182445, 0.02557391, -0.011276178, 0.017820733, 0.023670973, 0.0017293568, -0.0031501297, -0.0016192631, 0.01044043, 0.009071087, -0.014670604, -0.017820733, 0.010646152, 0.018656481, -0.010691154, -0.022410922, -0.017692156, -0.024391003, 0.010993309, -0.01088402, 0.0073545882, -0.000542433, -0.0028238662, 0.008569638, 0.0073481593, -0.027438272, 0.0018209678, -0.001176477, 0.00038673467, 0.010376141, 0.02259093, 0.03656722, 0.0010904913, 0.012581232, -0.0006497142, 0.010929021, 0.0024638514, 0.0135777015, 0.01380914, 0.009270381, 0.008486063, 0.01402772, -0.0069495714, 0.012414082, -0.032658488, 0.018785058, 0.013217687, 0.020842286, 0.016753547, -0.026743958, 0.021446597, -0.021112297, -0.008666071, 0.011591191, 0.025149606, 0.00043796445, 0.015699217, -0.0024863523, 0.020996578, 0.010954737, 0.022063766, 0.010948308, -0.024223853, -0.011944777, 0.0033365658, -0.010061128, -0.000753781, -0.019389369, -0.013616274, 0.004429468, -0.004220531, 0.0024043845, 0.016059233, -0.020276548, 0.024609584, 0.0053873644, -0.050222065, -0.0033654957, 0.0017872164, 0.009836119, -0.014567742, -0.0073545882, -0.0033719244, 0.009006799, 0.014850611, 0.033327084, -0.019157931, 0.026846819, 0.0026358226, -0.009758973, -0.023979558, -0.013783424, -0.00845392, 0.039473053, 0.007766034, 0.01595637, 0.0010205777, -0.022346634, 0.03584719, -0.018373612, 0.0006288205, -0.0005010474, -0.0043651797, 0.010871162, 0.035075728, -0.015673501, 0.004352322, -0.0023240242, 0.01530063, -0.0027097543, 0.00085583876, 0.015506352, -0.0063581187, 0.022655217, -0.004063024, 0.013886286, -0.0037640834, -0.010350426, 0.0055705863, 0.00433625, 0.014169155, 0.011700481, -0.011218319, 0.00019658175, 0.008691786, -0.0035390742, -0.028312594, 0.0018241822, 0.043330353, 0.022410922, 0.03155273, -0.0077403183, -0.025381044, -0.028724039, 0.021755181, -0.024995314, 0.008903937, -0.017036416, -0.009411816, -0.001938294, 0.0108904485, -0.017962167, -0.002576356, -0.024185281, 0.0028126156, 0.020726567, 0.010479002, -0.010118987, -0.018836489, 0.019350797, -0.028415455, 0.007746747, 0.006814566, 0.008762503, 0.032504193, 0.014207727, -0.01402772, -0.67847365, -0.033507094, -0.00029090483, -0.008595354, -0.0021568744, 0.0083382, -0.014002005, 0.0021022293, -0.014837753, 0.0018707912, -0.010787587, 0.004738052, -0.012041209, 0.0019800814, 0.019299366, -0.016252097, 0.020456556, -0.0022050908, -0.0056959484, 0.0107168695, -0.009379672, 0.012439798, 0.01794931, 0.0020475842, 0.008357487, -0.008209623, 0.02529104, 0.007328873, -0.012960534, 0.04567045, -0.04353608, 0.017242137, 0.015570641, -0.021999476, 0.05909386, 0.00601739, -0.019183647, 0.028364023, 0.016714973, 0.038341578, -0.035410028, -0.0023593828, 0.00016162495, 0.0012110319, -0.0011740661, -0.008209623, 0.011366182, -0.00021054437, 0.0005291735, -0.020199403, 0.0016039945, 0.015596356, 0.022449495, 0.0065574124, 0.00563166, -0.006898141, 0.0336871, 0.005004849, 0.010041841, 0.01855362, 0.0098489765, -0.0027499346, -0.0060334625, -0.01933794, 0.0019045427, 0.025805347, -0.012086212, 0.008003901, 0.01971081, -0.018913636, 0.009681826, -0.00043836626, -0.009816833, 0.01838647, -0.007361017, 0.021716608, 0.01756358, -0.0052555734, -0.01821932, 0.0291612, -0.0086339265, -0.0009860228, 0.000753781, -0.008762503, 0.033224225, -0.013796282, -0.016097805, 0.016599255, 0.010839017, 0.0010358462, 1.2945566e-05, 0.0053809355, 0.0031726304, -0.010922592, 0.0065349117, 0.0069174273, -0.019350797, -0.01044043, 0.016097805, -0.015197768, -0.008183908, -0.0035551463, 0.00601739, -0.004018022, -0.0124526555, 0.011160459, -0.012311221, 0.01065901, 0.043047484, -0.0035647894, -0.0030810195, -0.008588925, -0.0006641791, -0.0033654957, 0.029109769, -0.032761347, 0.01170691, 0.008048902, 0.010138274, 0.0050562792, 0.027952578, -0.015827794, 0.016059233, -0.007361017, 0.0027354697, 0.0075474535, -0.0108004445, -0.008434633, -0.01143047, 0.0044198246, 0.007881753, 0.0012696951, -0.007129579, -0.0007541828, -0.002476709, -0.0018836489, 0.02214091, -0.017357856, 0.006145967, -0.011803343, -0.014387735, -0.0062809726, 0.005397008, -0.015082049, 0.011494759, -0.012111926, -0.009733258, -0.004146599, -0.0012745167, 0.0041048117, -0.002721005, -0.026319655, -0.01115403, 0.03600148, 0.010080415, -0.0119319195, -0.013744852, -0.003100306, -0.018527905, -0.010138274, -0.0057120207, 0.0032208469, 0.0034651426, -0.0043716086, -0.011764769, -0.027515417, -0.017717872, 0.02016083, 0.0027226121, -0.015660644, -0.016072089, -0.017422145, 0.012066925, -0.0007702549, -0.0059016715, 0.0012351401, -0.01247837, -0.03695295, -0.0040790965, -0.016663542, 0.014349162, -0.0026968967, 0.007200296, 0.0045869746, 0.023709547, -0.00392159, -0.015390634, 0.032889925, -0.01766644, -0.020340838, -0.009533964, 0.010376141, 0.007258156, 0.0049502035, 0.012999106, -0.008762503, -0.016444962, 0.022372348, 0.044487543, 0.005975603, -0.016573539, 0.0058245254, 0.01722928, -0.009823261, 0.022449495, -0.022552356, 0.0006256061, -0.019260792, 0.00878179, 0.01650925, 0.0128833875, 0.004670549, -0.036104344, 0.004284819, -0.008685357, 0.024661014, 0.003854087, 0.022192342, 0.002960479, -0.0014167547, -0.0043491074, -0.00043515183, 0.0028029724, 0.0015139908, 0.00999684, -0.0006637773, 0.004946989, 0.042198878, -0.012999106, -0.014310589, -0.013108396, -0.0018482903, -0.0017052487, -0.0036708652, 0.00083173066, 0.0026952894, 0.035075728, -0.018013598, 0.023298101, 0.0064770523, -0.027155403, -0.0037351537, 0.049013443, -0.009636825, 0.019286508, 0.035384312, 0.02766971, -0.002584392, 0.0052748597, 0.0011652265, -0.025689628, -0.0066795605, -0.039138753, 0.02032798, 0.0145806, -0.0039280187, 0.0020250834, 0.0035808615, 0.031989887, 0.009701113, 0.02800401, 0.0016988199, 0.010350426, 0.01563493, 0.024159566, -0.007958899, -0.012330507, -0.01982653, -0.0025136748, 0.022295203, -0.0044133957, 0.0011660301, -0.0061041797, 0.002116694, 0.01595637, 0.0051848562, 0.009173949, 0.010067557, -0.0036547931, 0.013371979, -0.0017181064, -0.019453658, 0.019787956, 0.01049186, -0.0046287617, -0.015917799, -0.028801184, -0.0035197877, -0.012864101, 0.015583498, 0.0028174373, 0.028904047, 0.005593087, -0.007946041, -0.019196505, -0.0043651797, 0.012414082, -0.0017438218, 0.0126262335, 0.01590494, 0.009302526, 0.013783424, -0.01016399, -0.011623335, 0.011057598, 0.00336871, 0.005290932, -0.016252097, -0.0001511781, 0.009861834, -0.007598884, -0.0291612, -0.019260792, 0.0017952524, -0.012966962, 0.0030954846, -0.019839387, -0.019325081, 0.039627343, 0.0039698062, -0.006820995, -0.01496633, -0.027695425, -0.001907757, 0.048782006, 0.012941247, 0.0066152723, -0.010794016, 0.0019865104, -0.0013034465, -0.013963431, -0.031938456, 0.002452601, -0.01960795, -0.026203936, -0.0030617332, 0.007798178, -0.0039248043, 0.023156667, 0.00045443833, -0.00050546724, 0.0014416665, 0.0066024144, -0.004538758, 0.0023931342, -0.00081405137, 0.010427572, 0.009270381, 0.0062166844, -0.005538442, 0.026242508, 0.02689825, -0.003815514, -0.027926862, -0.01121189, 0.02738684, 0.0055480856, -0.009778259, -0.024558153, 0.012182644, -0.0078046066, 0.00070235034, 0.014207727, -0.0007156098, 0.04127313, 0.046261903, 0.009964695, -0.027052542, 0.000965129, 0.018695055, -0.0133205475, 0.012182644, -0.014194869, 0.0061170375, 0.034741428, -0.009746116, -0.025998212, -0.00040782927, 0.018245036, 0.016496394, -0.027078256, -0.012992677, -0.013011964, -0.052716453, -0.0011025454, -0.0029829799, -0.010530434, -0.011970492, 0.003944091, -0.020109398, 0.003384782, -0.0041176695, -0.02043084, -0.005049851, -0.0053166472, -0.022539498, -0.023902413, 0.0006392674, -0.0011202247, 0.018026456, -0.0049502035, -0.013204829, -0.0028110086, 0.041118834, 0.0005388168, -0.0005552907, -0.0032787062, -0.028595462, -0.021678034, -0.0025570695, -0.004953418, -0.008216052, -0.002645466, -0.0017213208, 0.032812778, 0.00955325, 0.006030248, 0.007444592, 0.00091932353, 0.0029942302, -0.0022822367, 0.023735262, -0.032118466, 0.013114825, 0.020070825, -0.024429576, -0.0045869746, -0.00455483, 0.013616274, -0.004345893, 0.017987883, 0.031064136, -0.05251073, 0.0071810097, 0.006435265, -0.012523373, 0.009411816, -0.0057313074, 0.0128833875, 0.003860516, -0.009386101, 0.010626866, -0.010755442, -0.029392637, 0.013384837, -0.030421251, 0.0063581187, 0.031321287, 0.01888792, 0.021163728, -0.01474775, -0.010376141, 0.004130527, -0.019170789, -0.00850535, 0.010350426, -0.0031244142, 0.010009698, -0.027541133, -0.0048312703, -0.015364918, 0.013005535, 0.0010085236, -0.021215158, -0.029624077, 0.0015147944, -0.0013934502, -0.01960795, -0.021189444, -0.032787062, -0.0092382375, 0.012227646, -0.003899089, 0.020070825, -0.0065188394, 0.01690784, -0.0012054067, 0.023542397, -0.01828361, -0.03422712, -0.01530063, 0.0027001111, 0.03103842, 0.023876697, 0.012375509, -0.022513783, 0.02512389, 0.0033558523, -0.02021226, -0.005577015, -0.018257894, -0.0109804515, -0.03417569, 0.008518208, 0.009051801, 0.018566478, -0.0074960226, -0.01551921, -0.017370714, -0.0097654015, -0.0041015972, -0.004821627, -0.009206093, -0.012934818, 0.0047573387, 0.0021504457, -0.015017761, 0.017074987, -0.0040276656, 0.008601783, 0.02921263, 0.0013902357, 0.019029355, 0.029135484, 0.020366551, -0.008003901, 0.005483797, -0.014130581, 0.008704644, -0.017345, -0.039473053, 0.014567742, -0.017332142, 0.030986989, 0.023825265, 0.030986989, -0.004847342, 0.015249198, -0.001099331, 0.016714973, -0.010832588, -0.024506722, 0.008871794, -0.021279447, -0.025213894, -0.032349903, -0.016149236, -0.044873275, 0.003699795, 0.016329244, -0.013500555, 0.0127998125, -0.012767668, -0.013963431, 0.0050466363, 0.016740689, 0.05073637, 0.009456818, 0.020790854, 0.006329189, -0.001030221, -0.019132216, 0.016046375, -0.018605052, -0.020353695, 0.019183647, 0.018360754, 0.011925491, 0.0006927071, 0.017460719, -0.002438136, -0.011726197, -0.02738684, 0.02307952, 0.0077403183, -0.012934818, -0.01253623, -0.00049100234, -0.014657746, 0.017062131, -0.01937651, -0.018540762, 0.008518208, -0.013989147, -0.024146708, 0.035410028, 0.0012648734, 0.030524112, 0.0101125585, -0.000100802135, -0.007991043, 0.0023674187, 0.019003639, 0.005081995, 0.0033044217, 0.0007702549, -0.011713339, 0.0045773312, -0.008344629, 0.0056284457, -0.019183647, 0.0074574496, 0.003429784, 0.02523961, -0.012491228, -0.022603787, -0.024172423, 0.003060126, 0.021112297, 0.0011587976, -0.002344918, -0.0133205475, -0.03157844, -0.016586397, 0.024866737, -0.015750648, 0.0067952797, -0.008183908, -0.0153134875, 0.0037640834, 0.003407283, -0.023516681, -0.0075860266, -0.023490967, -0.011282607, -0.013371979, 0.003799442, 0.016264955, 0.02622965, 0.016046375, 0.020173687, 0.016496394, -0.00045966177, 0.023015233, 0.0050594937, 0.012819099, -0.015390634, -0.0048794863, 0.0027049328, -0.03533288, -0.0043169633, -0.014953473, -0.0035519318, -0.025046745, 0.023683831, 0.025998212, -0.012291934, 0.014837753, 0.011481901, 0.040013075, -0.013886286, -0.021009436, -0.022436637, 0.025535336, -0.008093905, 0.011751912, 0.008955369, 0.0065027676, -0.018862205, -0.011173317, -0.009662541, 0.002531354, -0.025226751, -0.02275808, -0.0060945363, 0.026435373, -0.014182012, -0.020019395, -0.022950944, 0.013564844, -0.0056413035, 0.01838647, 0.00068828725, 0.004766982, 0.01518491, 0.02495674, 0.010408285, -0.0050980668, 0.007746747, -0.043998953, -0.014760607, -0.0047862683, -0.02666681, -0.008132477, -0.018630767, -0.008222481, 0.01369342, -0.027155403, -0.051893562, 0.0008445883, -0.01744786, -0.018373612, -0.021215158, -0.006444908, 0.0065477695, -0.0012954104, -0.022410922, 0.015982086, 0.007624599, 0.014824895, -0.008653213, -0.011436899, 0.010388999, 0.006891712, -0.008100334, 0.005644518, -0.0046930504, 0.00038974817, 0.020610848, 0.01563493, -0.010684725, 0.030524112, -0.013873428, 0.013166256, -0.018013598, 0.008511779, 0.008820363, 0.013912001, 0.0032545982, -0.008344629, -0.023400962, 0.012343365, 0.021652319, 0.02016083, -0.009302526, 0.023349533, -0.016817834, 0.022449495, -0.009450389, 0.013847712, -0.006454551, -0.012433369, 0.0084603485, -0.02529104, -0.036387213, 0.018913636, -0.03340423, -0.010041841, 0.002576356, 0.006454551, -0.018206464, 0.014156297, 0.04353608, -0.018129317, 0.02512389, 0.0030954846, 0.0074638785, -0.024352431, -0.0062713292, -0.0023111666, 0.0013500556, -0.014503454, 0.004622333, 0.003429784, -0.013031251, -0.009122518, -0.009077516, -0.0005717646, 0.001050311, -0.0011162066, -0.0028961906, -0.0073803035, 0.0033076361, -0.0013580916, -0.0042719613, -0.016740689, -0.0060977507, 0.011816201, 0.002783686, 0.009257523, 0.24110706, -0.019530803, 0.019080784, 0.031141281, -0.009926123, 0.007997472, -0.008704644, -0.013166256, 0.0015895297, 0.004783054, -0.0006718133, -0.001288178, -0.02766971, -0.0012037995, -0.0015871188, -0.002460637, -0.014452023, -0.007798178, -0.028415455, -0.04312463, -0.010704012, -0.025779633, -0.008473205, -6.790458e-05, 0.010832588, -0.0057055918, -0.013731994, 0.011256891, 0.031321287, 0.017589295, -0.010286137, -0.020366551, 0.0053037894, 0.00023967504, -0.010562577, -0.007836751, -0.0045805457, 0.007978185, 0.023092378, 0.042173162, -0.013294833, -0.0066088433, 0.012819099, -0.0016425676, -0.007759605, 0.010408285, -0.010408285, -0.020958005, -0.001645782, 0.018875062, -0.013204829, 0.018836489, 0.018103601, 0.039190184, -0.019067928, 0.005435581, 0.0010888841, 0.0035165732, -0.0037705123, 0.015416348, -0.006814566, 0.020469414, -0.009488962, 0.0027660066, -0.008312485, -0.0018643624, -0.020057969, -0.007643886, -0.0015292594, -0.010343997, -0.031166997, -0.0023433107, 0.01253623, -0.0037126527, -0.041658856, -0.02176804, 0.049116306, 0.003545503, 0.028415455, 0.04633905, -0.014927757, -0.014079151, -0.0033333513, -0.021896616, 0.011109028, -0.037210103, 0.031604156, 0.008396059, -0.016162094, 0.01535206, 9.638231e-05, -0.025972497, 0.016663542, 0.0046673347, -0.0002151651, 0.03162987, -0.01684355, 0.023130951, 0.0051719984, 0.009296097, -0.02959836, -0.0071938676, 0.0059241722, -0.0001261659, -0.014400592, 0.0012745167, -0.0126262335, -0.017705014, 0.015364918, -0.0024477793, -0.01684355, -0.012439798, 0.018707912, -0.026409658, -0.01281267, -0.010614008, 0.0191065, 0.0013757709, 0.0006967251, 0.014220585, 0.0054387953, -0.021935187, 0.016393531, 0.0092382375, -0.022796651, -0.013770566, -0.0058566695, 0.0042430316, 0.022989517, -0.015943512, 0.025278183, -0.005361649, 0.015043476, -0.025946781, -0.021588031, 0.020945147, 0.022848083, -0.008100334, 0.010266851, 0.009694684, 0.003008695, 0.004191601, 0.004738052, -0.008106762, 0.0073095863, -0.017589295, 0.0066731316, 0.0009281632, -0.012021923, -0.010086844, -0.038984463, -0.004480899, 0.0024943883, -0.014722034, 0.010974023, -0.0127998125, -0.024943883, -0.030678404, 0.002169732, 0.022719506, -0.024712445, -0.0071938676, 0.0031276287, -0.027361125, -0.035924334, -0.0074767363, -0.16581254, 0.03026696, 0.017267853, -0.007759605, 0.0019350796, 0.013256259, 0.0101961335, -0.0062038265, -0.0066667027, -8.598568e-05, 0.02307952, 0.0005066726, -0.054927975, -0.0023593828, 0.013018393, 0.010710441, -0.010678296, 0.017679298, -0.001503544, 0.017087845, 0.015146337, -0.0063099023, -0.003600148, 0.014837753, -0.023812408, 0.006522054, -0.013886286, 0.028029725, -0.025136748, 0.012671236, -0.032221325, -0.011546189, 0.027772572, 0.017525006, 0.0054580816, -0.0027338625, 0.019247934, -0.0170107, 0.016637828, 0.006377405, 0.013487698, 0.02766971, 0.00039898962, 0.00944396, -0.018193606, 0.014696319, 0.0041273125, -0.015750648, 0.029521214, -0.0050080633, -0.018347898, -0.011880489, 0.022475211, 0.006583128, 0.0134105515, 0.026435373, 0.002676003, 0.0022645574, 0.016612113, -0.0057570226, 0.019646522, -0.03026696, 0.0012303184, -0.025856778, -0.002221163, -0.022436637, -0.012304792, 0.003423355, -0.008582496, 0.023015233, -0.000782309, -0.004821627, 0.026795387, -0.011301894, 0.0069560003, 0.013461983, -0.01530063, 0.023426678, 0.006554198, -0.0038122996, -0.016046375, 0.02098372, 0.00017739569, 0.015506352, 0.009630396, 0.022873798, 0.0132305445, -0.0012857672, -0.018566478, -0.0075024515, 0.04811341, -0.017717872, -0.010009698, 0.004384466, -0.002184197, 0.008511779, -0.015506352, 0.0058952426, -0.0062102554, -0.027772572, -0.0063870484, -0.015943512, -0.009726829, 0.008351058, 0.020996578, 0.008813934, 0.011829058, 0.0077017453, 0.029778369, -0.015043476, -0.0073803035, 0.0132305445, 0.009013228, 0.029906945, 0.003568004, 0.035692897, -0.014902041, -0.0030954846, -0.008415346, -0.00767603, 0.05533942, -0.013500555, -0.008601783, 0.0085503515, -0.01513348, -0.010144703, -0.058888137, -0.031141281, 0.0239667, 0.023259528, -0.008537494, 0.005397008, 0.0045355437, 0.015082049, -0.029418353, 0.016856408, -0.0056927344, -0.015827794, -0.012966962, -0.004468041, 0.038007278, -0.022873798, -0.009116089, 0.005233072, -0.013731994, 0.0239667, -0.025831062, -0.0012889816, 0.0011113851, -0.009681826, -0.0065477695, 0.0015903333, -0.04585046, 0.014734892, 0.0066538453, 0.010607579, -0.0043748226, 0.0013404123, 0.008293198, -0.021279447, -0.022449495, -0.010652581, -0.023825265, -0.006859568, 0.020585133, -0.030035522, 0.012156929, -0.00090244785, -0.0010896877, -0.021498026, -0.010646152, -0.005898457, -0.0038476584, 0.017306427, 0.00065453583, -0.031681303, -0.018913636, -0.024095276, -0.03155273, 0.023555255, 0.025561051, -0.021125155, 0.014477738, 0.021793753, 0.018836489, 0.005840597, 0.012555516, -0.00025313543, -0.023696689, 0.019633666, 0.013359121, 0.018990781, -0.026769673, 0.003452285, 0.012465512, 0.0035326453, -0.0028511886, 0.025329614, -0.016496394, 0.009861834, -0.010999738, -0.008537494, -0.008736788, -0.01236908, 0.018707912, -0.006512411, -0.00576988, -0.02700111, 0.002184197, 0.0014633638, 0.024095276, 0.01717785, 0.011546189, -0.018309325, -0.009598252, -0.016316386, -0.0052427156, 0.018759344, 0.00472198, -0.018849347, -0.014053435, -0.0061266804, 0.0035326453, -0.0066152723, 0.0032176324, 0.0042494605, 6.438881e-05, -0.018322183, -0.07154009, 0.021960903, -0.0071488656, -0.026923966, 0.015660644, -0.0101125585, 0.008813934, -0.026641095, 0.012876959, -0.011790485, -0.006885283, 0.016136378, -0.0010792408, 0.012221217, -0.004378037, -0.00635169, 0.035307165, -0.0033815678, 0.00850535, 0.010549719, 0.0059788176, 0.0037705123, 0.020289406, -0.014812038, 0.019312223, -0.0035776473, -0.012439798, 0.019800814, -0.033275656, 0.0011571904, -0.0046962644, -0.037827272, 1.2041511e-05, 0.023053806, -0.0024799234, -0.033661384, 0.012407653, 0.009855405, 0.013307691, 0.0065895566, -0.01694641, -0.033095647, 0.01888792, -0.029701222, -0.019852245, -0.0050466363, -0.017306427, 0.011835487, 0.022012334, -0.0020925861, 0.01690784, 0.027309695, -0.02302809, -0.00051189604, 0.019967964, -0.055905156, 0.028646892, 0.028955476, 0.0015509566, -7.850211e-05, 0.023696689, 0.010929021, 0.012613376, -0.017692156, -0.00037447968, 0.009983982, -0.011141173, -0.008801077, 0.0015887261, -0.03440713, -0.009386101, 0.0063806195, 0.002992623, 0.009701113, 0.0066859894, 0.0031019133, -0.0063034734, 0.008498921, -0.026242508, 0.023606686, 0.013513413, 0.0017454289, -0.008376773, -0.00201544, 0.048164837, 0.0074188765, -0.0010181669, -0.017190708, 0.008029616, 0.029572645, -0.025008172, -0.005091638, -0.024866737, 0.007271013, -0.002328846, 0.0062713292, -0.016894981, 7.393161e-05, 0.022732364, 0.012375509, 0.0014272016, 1.2298916e-05, -0.0191065, -0.016637828, -0.01452917, -0.012137642, -0.02307952, -0.0001341015, 0.004043738, 0.024545295, 0.014516312, -0.01766644, -0.020893717, 0.009263952, 0.008563209, 0.0018948994, -0.013500555, -0.0034780002, -0.015814936, 0.044204675, 0.008093905, 0.007367446, 0.011366182, 0.004853771, 0.0030826267, -0.0080231875, -0.006621701, -0.03985878, 0.007791749, -0.00018603443, -0.0026872533, -0.016419247, -0.008408917, -0.027489703, -0.024545295, 0.0034490705, -0.020456556, 0.010427572, 0.01578922, 0.04991348, -0.0014159511, -0.005191285, 0.021253731, 0.00052837, 0.03108985, 0.0034940722, 0.0030553043, 0.0004680996, -0.009630396, 0.0140148625, -0.031115565, -0.013976289, -0.007766034, -0.021742323, -0.0062552574, -0.017164992, 0.013513413, -0.025535336, -0.006444908, 0.027412556, 0.0075345957, 0.01264552, -0.0009112875, -0.029315492, -0.021215158, 0.028801184, -0.0032497765, -0.020687994, -0.03129557, 0.0037962275, -0.001365324, -0.02805544, -0.005638089, 0.02689825, -0.007695317, -0.0027724355, -0.00074895937, -0.0056798765, 0.0045580445, -0.008325342, -0.008858936, -0.0070717195, -0.020276548, 0.03600148, -0.0047123367, -0.016599255, 0.01573779, -0.028595462]}], "model": "text-embedding-ada-002-v2", "usage": {"prompt_tokens": 3, "total_tokens": 3}}
\ No newline at end of file
diff --git a/tests/data/rsp_cache.json b/tests/data/rsp_cache.json
index db452f676..eb67021a5 100644
--- a/tests/data/rsp_cache.json
+++ b/tests/data/rsp_cache.json
@@ -141,5 +141,239 @@
"\nRole: You are a senior development and qa engineer, your role is summarize the code running result.\nIf the running result does not include an error, you should explicitly approve the result.\nOn the other hand, if the running result indicates some error, you should point out which part, the development code or the test code, produces the error,\nand give specific instructions on fixing the errors. Here is the code info:\n\n## Development Code File Name\na.txt\n## Development Code\n```python\nprint('Hello, World')\n```\n## Test File Name\n\n## Test Code\n```python\nNone\n```\n## Running Command\n\n## Running Output\nstandard output: \n```text\n\n```\nstandard errors: \n```text\n\n```\n\nNow you should begin your analysis\n---\n## instruction:\nPlease summarize the cause of the errors and give correction instruction\n## File To Rewrite:\nDetermine the ONE file to rewrite in order to fix the error, for example, xyz.py, or test_xyz.py\n## Status:\nDetermine if all of the code works fine, if so write PASS, else FAIL,\nWRITE ONLY ONE WORD, PASS OR FAIL, IN THIS SECTION\n## Send To:\nPlease write Engineer if the errors are due to problematic development codes, and QaEngineer to problematic test codes, and NoOne if there are no errors,\nWRITE ONLY ONE WORD, Engineer OR QaEngineer OR NoOne, IN THIS SECTION.\n---\nYou should fill in necessary instruction, status, send to, and finally return all content between the --- segment line.\n": "---\ninstruction: There are no errors in the provided code.\n\nFile To Rewrite: N/A\n\nStatus: PASS\n\nSend To: NoOne\n---",
"\nRole: You are a senior development and qa engineer, your role is summarize the code running result.\nIf the running result does not include an error, you should explicitly approve the result.\nOn the other hand, if the running result indicates some error, you should point out which part, the development code or the test code, produces the error,\nand give specific instructions on fixing the errors. Here is the code info:\n\n## Development Code File Name\na.sh\n## Development Code\n```python\necho 'Hello World'\n```\n## Test File Name\n\n## Test Code\n```python\nNone\n```\n## Running Command\necho Hello World\n## Running Output\nstandard output: \n```text\nHello World\n\n```\nstandard errors: \n```text\n\n```\n\nNow you should begin your analysis\n---\n## instruction:\nPlease summarize the cause of the errors and give correction instruction\n## File To Rewrite:\nDetermine the ONE file to rewrite in order to fix the error, for example, xyz.py, or test_xyz.py\n## Status:\nDetermine if all of the code works fine, if so write PASS, else FAIL,\nWRITE ONLY ONE WORD, PASS OR FAIL, IN THIS SECTION\n## Send To:\nPlease write Engineer if the errors are due to problematic development codes, and QaEngineer to problematic test codes, and NoOne if there are no errors,\nWRITE ONLY ONE WORD, Engineer OR QaEngineer OR NoOne, IN THIS SECTION.\n---\nYou should fill in necessary instruction, status, send to, and finally return all content between the --- segment line.\n": "The running result indicates no error. The code works fine.\n\n## File To Rewrite:\nNo file needs to be rewritten.\n\n## Status:\nPASS\n\n## Send To:\nNoOne\n\n---",
"\nRole: You are a senior development and qa engineer, your role is summarize the code running result.\nIf the running result does not include an error, you should explicitly approve the result.\nOn the other hand, if the running result indicates some error, you should point out which part, the development code or the test code, produces the error,\nand give specific instructions on fixing the errors. Here is the code info:\n\n## Development Code File Name\na.py\n## Development Code\n```python\npython -c \"print(1/0)\"\n```\n## Test File Name\n\n## Test Code\n```python\nNone\n```\n## Running Command\npython -c print(1/0)\n## Running Output\nstandard output: \n```text\n\n```\nstandard errors: \n```text\nTraceback (most recent call last):\n File \"\", line 1, in \nZeroDivisionError: division by zero\n\n```\n\nNow you should begin your analysis\n---\n## instruction:\nPlease summarize the cause of the errors and give correction instruction\n## File To Rewrite:\nDetermine the ONE file to rewrite in order to fix the error, for example, xyz.py, or test_xyz.py\n## Status:\nDetermine if all of the code works fine, if so write PASS, else FAIL,\nWRITE ONLY ONE WORD, PASS OR FAIL, IN THIS SECTION\n## Send To:\nPlease write Engineer if the errors are due to problematic development codes, and QaEngineer to problematic test codes, and NoOne if there are no errors,\nWRITE ONLY ONE WORD, Engineer OR QaEngineer OR NoOne, IN THIS SECTION.\n---\nYou should fill in necessary instruction, status, send to, and finally return all content between the --- segment line.\n": "---\nThe error is caused by the development code in file a.py, which attempts to divide by zero. To fix the error, the development code in a.py should be modified to handle the ZeroDivisionError, for example by using a try-except block.\n\nFile To Rewrite:\na.py\n\nStatus:\nFAIL\n\nSend To:\nEngineer\n---",
- "\nNOTICE\n1. Role: You are a Development Engineer or QA engineer;\n2. Task: You received this message from another Development Engineer or QA engineer who ran or tested your code. \nBased on the message, first, figure out your own role, i.e. Engineer or QaEngineer,\nthen rewrite the development code or the test code based on your role, the error, and the summary, such that all bugs are fixed and the code performs well.\nAttention: Use '##' to split sections, not '#', and '## ' SHOULD WRITE BEFORE the test case or script and triple quotes.\nThe message is as follows:\n# Legacy Code\n```python\n\nfrom typing import List\nfrom deck import Deck\nfrom card import Card\n\nclass Player:\n \"\"\"\n A class representing a player in the Black Jack game.\n \"\"\"\n\n def __init__(self, name: str):\n \"\"\"\n Initialize a Player object.\n \n Args:\n name (str): The name of the player.\n \"\"\"\n self.name = name\n self.hand: List[Card] = []\n self.score = 0\n\n def draw(self, deck: Deck):\n \"\"\"\n Draw a card from the deck and add it to the player's hand.\n \n Args:\n deck (Deck): The deck of cards.\n \"\"\"\n card = deck.draw_card()\n self.hand.append(card)\n self.calculate_score()\n\n def calculate_score(self) -> int:\n \"\"\"\n Calculate the score of the player's hand.\n \n Returns:\n int: The score of the player's hand.\n \"\"\"\n self.score = sum(card.value for card in self.hand)\n # Handle the case where Ace is counted as 11 and causes the score to exceed 21\n if self.score > 21 and any(card.rank == 'A' for card in self.hand):\n self.score -= 10\n return self.score\n\n```\n---\n# Unit Test Code\n```python\n\nimport unittest\nfrom blackjack_game.player import Player\nfrom blackjack_game.deck import Deck\nfrom blackjack_game.card import Card\n\nclass TestPlayer(unittest.TestCase):\n ## Test the Player's initialization\n def test_player_initialization(self):\n player = Player(\"Test Player\")\n self.assertEqual(player.name, \"Test Player\")\n self.assertEqual(player.hand, [])\n self.assertEqual(player.score, 0)\n\n ## Test the Player's draw method\n def test_player_draw(self):\n deck = Deck()\n player = Player(\"Test Player\")\n player.draw(deck)\n self.assertEqual(len(player.hand), 1)\n self.assertEqual(player.score, player.hand[0].value)\n\n ## Test the Player's calculate_score method\n def test_player_calculate_score(self):\n deck = Deck()\n player = Player(\"Test Player\")\n player.draw(deck)\n player.draw(deck)\n self.assertEqual(player.score, sum(card.value for card in player.hand))\n\n ## Test the Player's calculate_score method with Ace card\n def test_player_calculate_score_with_ace(self):\n deck = Deck()\n player = Player(\"Test Player\")\n player.hand.append(Card('A', 'Hearts', 11))\n player.hand.append(Card('K', 'Hearts', 10))\n player.calculate_score()\n self.assertEqual(player.score, 21)\n\n ## Test the Player's calculate_score method with multiple Aces\n def test_player_calculate_score_with_multiple_aces(self):\n deck = Deck()\n player = Player(\"Test Player\")\n player.hand.append(Card('A', 'Hearts', 11))\n player.hand.append(Card('A', 'Diamonds', 11))\n player.calculate_score()\n self.assertEqual(player.score, 12)\n\nif __name__ == '__main__':\n unittest.main()\n\n\n```\n---\n# Console logs\n```text\n\n```\n---\nNow you should start rewriting the code:\n## file name of the code to rewrite: Write code with triple quote. Do your best to implement THIS IN ONLY ONE FILE.\n": "```python\n## Development Engineer\n\nfrom typing import List\nfrom deck import Deck\nfrom card import Card\n\nclass Player:\n \"\"\"\n A class representing a player in the Black Jack game.\n \"\"\"\n\n def __init__(self, name: str):\n \"\"\"\n Initialize a Player object.\n \n Args:\n name (str): The name of the player.\n \"\"\"\n self.name = name\n self.hand: List[Card] = []\n self.score = 0\n\n def draw(self, deck: Deck):\n \"\"\"\n Draw a card from the deck and add it to the player's hand.\n \n Args:\n deck (Deck): The deck of cards.\n \"\"\"\n card = deck.draw_card()\n self.hand.append(card)\n self.calculate_score()\n\n def calculate_score(self) -> int:\n \"\"\"\n Calculate the score of the player's hand.\n \n Returns:\n int: The score of the player's hand.\n \"\"\"\n self.score = sum(card.value for card in self.hand)\n # Handle the case where Ace is counted as 11 and causes the score to exceed 21\n ace_count = sum(1 for card in self.hand if card.rank == 'A')\n while self.score > 21 and ace_count > 0:\n self.score -= 10\n ace_count -= 1\n return self.score\n```\n\n```python\n## QaEngineer\n\nimport unittest\nfrom player import Player\nfrom deck import Deck\nfrom card import Card\n\nclass TestPlayer(unittest.TestCase):\n ## Test the Player's initialization\n def test_player_initialization(self):\n player = Player(\"Test Player\")\n self.assertEqual(player.name, \"Test Player\")\n self.assertEqual(player.hand, [])\n self.assertEqual(player.score, 0)\n\n ## Test the Player's draw method\n def test_player_draw(self):\n deck = Deck()\n player = Player(\"Test Player\")\n player.draw(deck)\n self.assertEqual(len(player.hand), 1)\n self.assertEqual(player.score, player.hand[0].value)\n\n ## Test the Player's calculate_score method\n def test_player_calculate_score(self):\n deck = Deck()\n player = Player(\"Test Player\")\n player.draw(deck)\n player.draw(deck)\n self.assertEqual(player.score, sum(card.value for card in player.hand))\n\n ## Test the Player's calculate_score method with Ace card\n def test_player_calculate_score_with_ace(self):\n player = Player(\"Test Player\")\n player.hand.append(Card('A', 'Hearts', 11))\n player.hand.append(Card('K', 'Hearts', 10))\n player.calculate_score()\n self.assertEqual(player.score, 21)\n\n ## Test the Player's calculate_score method with multiple Aces\n def test_player_calculate_score_with_multiple_aces(self):\n player = Player(\"Test Player\")\n player.hand.append(Card('A', 'Hearts', 11))\n player.hand.append(Card('A', 'Diamonds', 11))\n player.calculate_score()\n self.assertEqual(player.score, 12)\n\nif __name__ == '__main__':\n unittest.main()\n```"
+ "\nNOTICE\n1. Role: You are a Development Engineer or QA engineer;\n2. Task: You received this message from another Development Engineer or QA engineer who ran or tested your code. \nBased on the message, first, figure out your own role, i.e. Engineer or QaEngineer,\nthen rewrite the development code or the test code based on your role, the error, and the summary, such that all bugs are fixed and the code performs well.\nAttention: Use '##' to split sections, not '#', and '## ' SHOULD WRITE BEFORE the test case or script and triple quotes.\nThe message is as follows:\n# Legacy Code\n```python\n\nfrom typing import List\nfrom deck import Deck\nfrom card import Card\n\nclass Player:\n \"\"\"\n A class representing a player in the Black Jack game.\n \"\"\"\n\n def __init__(self, name: str):\n \"\"\"\n Initialize a Player object.\n \n Args:\n name (str): The name of the player.\n \"\"\"\n self.name = name\n self.hand: List[Card] = []\n self.score = 0\n\n def draw(self, deck: Deck):\n \"\"\"\n Draw a card from the deck and add it to the player's hand.\n \n Args:\n deck (Deck): The deck of cards.\n \"\"\"\n card = deck.draw_card()\n self.hand.append(card)\n self.calculate_score()\n\n def calculate_score(self) -> int:\n \"\"\"\n Calculate the score of the player's hand.\n \n Returns:\n int: The score of the player's hand.\n \"\"\"\n self.score = sum(card.value for card in self.hand)\n # Handle the case where Ace is counted as 11 and causes the score to exceed 21\n if self.score > 21 and any(card.rank == 'A' for card in self.hand):\n self.score -= 10\n return self.score\n\n```\n---\n# Unit Test Code\n```python\n\nimport unittest\nfrom blackjack_game.player import Player\nfrom blackjack_game.deck import Deck\nfrom blackjack_game.card import Card\n\nclass TestPlayer(unittest.TestCase):\n ## Test the Player's initialization\n def test_player_initialization(self):\n player = Player(\"Test Player\")\n self.assertEqual(player.name, \"Test Player\")\n self.assertEqual(player.hand, [])\n self.assertEqual(player.score, 0)\n\n ## Test the Player's draw method\n def test_player_draw(self):\n deck = Deck()\n player = Player(\"Test Player\")\n player.draw(deck)\n self.assertEqual(len(player.hand), 1)\n self.assertEqual(player.score, player.hand[0].value)\n\n ## Test the Player's calculate_score method\n def test_player_calculate_score(self):\n deck = Deck()\n player = Player(\"Test Player\")\n player.draw(deck)\n player.draw(deck)\n self.assertEqual(player.score, sum(card.value for card in player.hand))\n\n ## Test the Player's calculate_score method with Ace card\n def test_player_calculate_score_with_ace(self):\n deck = Deck()\n player = Player(\"Test Player\")\n player.hand.append(Card('A', 'Hearts', 11))\n player.hand.append(Card('K', 'Hearts', 10))\n player.calculate_score()\n self.assertEqual(player.score, 21)\n\n ## Test the Player's calculate_score method with multiple Aces\n def test_player_calculate_score_with_multiple_aces(self):\n deck = Deck()\n player = Player(\"Test Player\")\n player.hand.append(Card('A', 'Hearts', 11))\n player.hand.append(Card('A', 'Diamonds', 11))\n player.calculate_score()\n self.assertEqual(player.score, 12)\n\nif __name__ == '__main__':\n unittest.main()\n\n\n```\n---\n# Console logs\n```text\n\n```\n---\nNow you should start rewriting the code:\n## file name of the code to rewrite: Write code with triple quote. Do your best to implement THIS IN ONLY ONE FILE.\n": "```python\n## Development Engineer\n\nfrom typing import List\nfrom deck import Deck\nfrom card import Card\n\nclass Player:\n \"\"\"\n A class representing a player in the Black Jack game.\n \"\"\"\n\n def __init__(self, name: str):\n \"\"\"\n Initialize a Player object.\n \n Args:\n name (str): The name of the player.\n \"\"\"\n self.name = name\n self.hand: List[Card] = []\n self.score = 0\n\n def draw(self, deck: Deck):\n \"\"\"\n Draw a card from the deck and add it to the player's hand.\n \n Args:\n deck (Deck): The deck of cards.\n \"\"\"\n card = deck.draw_card()\n self.hand.append(card)\n self.calculate_score()\n\n def calculate_score(self) -> int:\n \"\"\"\n Calculate the score of the player's hand.\n \n Returns:\n int: The score of the player's hand.\n \"\"\"\n self.score = sum(card.value for card in self.hand)\n # Handle the case where Ace is counted as 11 and causes the score to exceed 21\n ace_count = sum(1 for card in self.hand if card.rank == 'A')\n while self.score > 21 and ace_count > 0:\n self.score -= 10\n ace_count -= 1\n return self.score\n```\n\n```python\n## QaEngineer\n\nimport unittest\nfrom player import Player\nfrom deck import Deck\nfrom card import Card\n\nclass TestPlayer(unittest.TestCase):\n ## Test the Player's initialization\n def test_player_initialization(self):\n player = Player(\"Test Player\")\n self.assertEqual(player.name, \"Test Player\")\n self.assertEqual(player.hand, [])\n self.assertEqual(player.score, 0)\n\n ## Test the Player's draw method\n def test_player_draw(self):\n deck = Deck()\n player = Player(\"Test Player\")\n player.draw(deck)\n self.assertEqual(len(player.hand), 1)\n self.assertEqual(player.score, player.hand[0].value)\n\n ## Test the Player's calculate_score method\n def test_player_calculate_score(self):\n deck = Deck()\n player = Player(\"Test Player\")\n player.draw(deck)\n player.draw(deck)\n self.assertEqual(player.score, sum(card.value for card in player.hand))\n\n ## Test the Player's calculate_score method with Ace card\n def test_player_calculate_score_with_ace(self):\n player = Player(\"Test Player\")\n player.hand.append(Card('A', 'Hearts', 11))\n player.hand.append(Card('K', 'Hearts', 10))\n player.calculate_score()\n self.assertEqual(player.score, 21)\n\n ## Test the Player's calculate_score method with multiple Aces\n def test_player_calculate_score_with_multiple_aces(self):\n player = Player(\"Test Player\")\n player.hand.append(Card('A', 'Hearts', 11))\n player.hand.append(Card('A', 'Diamonds', 11))\n player.calculate_score()\n self.assertEqual(player.score, 12)\n\nif __name__ == '__main__':\n unittest.main()\n```",
+ "\n # Context:\n user: \n## User Requirement\nThis is a titanic passenger survival dataset, your goal is to predict passenger survival outcome. The target column is Survived. Perform data analysis, data preprocessing, feature engineering, and modeling to predict the target. Report accuracy on the eval data. Train data path: 'tests/data/ml_datasets/titanic/split_train.csv', eval data path: 'tests/data/ml_datasets/titanic/split_eval.csv'.\n## Context\n\n## Current Plan\n[]\n## Current Task\n{}\n\n # Task:\n Based on the context, write a plan or modify an existing plan of what you should do to achieve the goal. A plan consists of one to 3 tasks.\n If you are modifying an existing plan, carefully follow the instruction, don't make unnecessary changes. Give the whole plan unless instructed to modify only one task of the plan.\n If you encounter errors on the current task, revise and output the current single task only.\n Output a list of jsons following the format:\n ```json\n [\n {\n \"task_id\": str = \"unique identifier for a task in plan, can be an ordinal\",\n \"dependent_task_ids\": list[str] = \"ids of tasks prerequisite to this task\",\n \"instruction\": \"what you should do in this task, one short phrase or sentence\",\n },\n ...\n ]\n ```\n ": "```json\n[\n {\n \"task_id\": \"1\",\n \"dependent_task_ids\": [],\n \"instruction\": \"Perform exploratory data analysis on the train dataset to understand the features and target variable.\"\n },\n {\n \"task_id\": \"2\",\n \"dependent_task_ids\": [\"1\"],\n \"instruction\": \"Preprocess the data by handling missing values, encoding categorical variables, and scaling features.\"\n },\n {\n \"task_id\": \"3\",\n \"dependent_task_ids\": [\"2\"],\n \"instruction\": \"Conduct feature engineering to create new features that may help improve model performance.\"\n },\n {\n \"task_id\": \"4\",\n \"dependent_task_ids\": [\"3\"],\n \"instruction\": \"Select and train a machine learning model using the processed train dataset.\"\n },\n {\n \"task_id\": \"5\",\n \"dependent_task_ids\": [\"4\"],\n \"instruction\": \"Evaluate the model's accuracy using the eval dataset and report the results.\"\n }\n]\n```",
+ "[{\"role\": \"user\", \"content\": \"\\nPlease assign a task type to each task in the list below from the given categories:\\nTask 1: Perform exploratory data analysis on the train dataset to understand the features and target variable.\\nTask 2: Preprocess the data by handling missing values, encoding categorical variables, and scaling features.\\nTask 3: Conduct feature engineering to create new features that may help improve model performance.\\nTask 4: Select and train a machine learning model using the processed train dataset.\\nTask 5: Evaluate the model's accuracy using the eval dataset and report the results.\\n\\n## All Task Type:\\n- **eda**: For performing exploratory data analysis\\n- **data_preprocess**: Only for changing value inplace.\\n- **feature_engineering**: Only for creating new columns for input data.\\n- **model_train**: Only for training model.\\n- **model_evaluate**: Only for evaluating model.\\n- **stable_diffusion**: Related to text2image, image2image using stable diffusion model.\\n- **image2webpage**: For converting image into webpage code.\\n- **web_scraping**: For scraping data from web pages.\\n- **other**: Any tools not in the defined categories\\n\"}]": {
+ "task_type": [
+ "eda",
+ "data_preprocess",
+ "feature_engineering",
+ "model_train",
+ "model_evaluate"
+ ]
+ },
+ "[{\"role\": \"user\", \"content\": \"\\n# Background\\nAs a data scientist, you need to help user to achieve their goal [This is a titanic passenger survival dataset, your goal is to predict passenger survival outcome. The target column is Survived. Perform data analysis, data preprocessing, feature engineering, and modeling to predict the target. Report accuracy on the eval data. Train data path: 'tests/data/ml_datasets/titanic/split_train.csv', eval data path: 'tests/data/ml_datasets/titanic/split_eval.csv'.] step-by-step in an continuous Jupyter notebook.\\n\\n## Done Tasks\\n```python\\n\\n```end\\n\\n## Current Task\\nPerform exploratory data analysis on the train dataset to understand the features and target variable.\\n\\n# Latest Data Info\\nLatest data info after previous tasks:\\n\\n\\n# Task\\nWrite complete code for 'Current Task'. And avoid duplicating code from 'Done Tasks', such as repeated import of packages, reading data, etc.\\nSpecifically, \\n\\n# Code Steps:\\nStrictly follow steps below when you writing code if it's convenient.\\n\\n\\n# Output Example:\\nwhen current task is \\\"train a lightgbm model on training data\\\", and their are two steps in 'Code Steps', the code be like:\\n```python\\n# Step 1: check data type and convert to numeric\\nojb_cols = train.select_dtypes(include='object').columns.tolist()\\n\\nfor col in obj_cols:\\n encoder = LabelEncoder()\\n train[col] = encoder.fit_transform(train[col].unique().tolist() + ['unknown'])\\n test[col] = test[col].apply(lambda x: x if x in encoder.classes_ else 'unknown')\\n test[col] = encoder.transform(test[col])\\n\\n# Step 2: train lightgbm model\\nmodel = LGBMClassifier()\\nmodel.fit(train, y_train)\\n```end\\n\\n# Constraints:\\n- Ensure the output new code is executable in the same Jupyter notebook with previous tasks code have been executed.\\n- The output code should contain all steps implemented in 'Code Steps'.\\n\"}]": {
+ "code": "# Step 1: Load the training dataset\nimport pandas as pd\ntrain_data = pd.read_csv('tests/data/ml_datasets/titanic/split_train.csv')\n\n# Step 2: Display the first few rows of the dataset\ndisplay(train_data.head())\n\n# Step 3: Get a summary of the dataset\ntrain_data.info()\n\n# Step 4: Describe the numerical features\ntrain_data.describe()\n\n# Step 5: Describe the categorical features\ntrain_data.describe(include=['O'])\n\n# Step 6: Check for missing values\nmissing_values = train_data.isnull().sum()\nmissing_values[missing_values > 0]\n\n# Step 7: Visualize the distribution of the target variable 'Survived'\nimport seaborn as sns\nimport matplotlib.pyplot as plt\nsns.countplot(x='Survived', data=train_data)\nplt.title('Distribution of Survival')\nplt.show()\n\n# Step 8: Explore the relationship between features and the target variable\n# Visualize the survival rate by sex\nsns.barplot(x='Sex', y='Survived', data=train_data)\nplt.title('Survival Rate by Sex')\nplt.show()\n\n# Visualize the survival rate by class\nsns.barplot(x='Pclass', y='Survived', data=train_data)\nplt.title('Survival Rate by Class')\nplt.show()\n\n# Visualize the survival rate by embarkation port\nsns.barplot(x='Embarked', y='Survived', data=train_data)\nplt.title('Survival Rate by Embarkation Port')\nplt.show()"
+ },
+ "[{\"role\": \"user\", \"content\": \"\\n# Background\\nKeep dataset column information updated before model train.\\n## Done Tasks\\n```python\\nimport pandas as pd\\ntrain_data = pd.read_csv('tests/data/ml_datasets/titanic/split_train.csv')\\ndisplay(train_data.head())\\ntrain_data.info()\\ntrain_data.describe()\\ntrain_data.describe(include=['O'])\\nmissing_values = train_data.isnull().sum()\\nmissing_values[missing_values > 0]\\nimport seaborn as sns\\nimport matplotlib.pyplot as plt\\nsns.countplot(x='Survived', data=train_data)\\nplt.title('Distribution of Survival')\\nplt.show()\\nsns.barplot(x='Sex', y='Survived', data=train_data)\\nplt.title('Survival Rate by Sex')\\nplt.show()\\nsns.barplot(x='Pclass', y='Survived', data=train_data)\\nplt.title('Survival Rate by Class')\\nplt.show()\\nsns.barplot(x='Embarked', y='Survived', data=train_data)\\nplt.title('Survival Rate by Embarkation Port')\\nplt.show()\\n```end\\n\\n# Task\\nUpdate and print the dataset's column information only if the train or test data has changed. Use the following code:\\n```python\\nfrom metagpt.tools.libs.data_preprocess import get_column_info\\n\\ncolumn_info = get_column_info(df)\\nprint(\\\"column_info\\\")\\nprint(column_info)\\n```end\\n\\n# Constraints:\\n- Use the DataFrame variable from 'Done Tasks' in place of df.\\n- Import `get_column_info` only if it's not already imported.\\n\"}]": {
+ "code": "from metagpt.tools.libs.data_preprocess import get_column_info\n\ncolumn_info = get_column_info(train_data)\nprint(\"column_info\")\nprint(column_info)"
+ },
+ "[{\"role\": \"user\", \"content\": \"\\n## User Requirement:\\nPreprocess the data by handling missing values, encoding categorical variables, and scaling features.\\n\\n## Task\\nRecommend up to five tools from 'Available Tools' that can help solve the 'User Requirement'. \\nThis is a detailed code steps for current task. You can refer to it when recommending tools.\\n\\n\\n## Available Tools:\\n{'FillMissingValue': 'Completing missing values with simple strategies'}\\n\\n## Tool Selection and Instructions:\\n- Select tools most relevant to completing the 'User Requirement'.\\n- If you believe that no tools are suitable, indicate with an empty list.\\n- Only list the names of the tools, not the full schema of each tool.\\n- Ensure selected tools are listed in 'Available Tools'.\\n\"}]": {
+ "recommend_tools": [
+ "FillMissingValue"
+ ]
+ },
+ "[{\"role\": \"user\", \"content\": \"\\n# Background\\nAs a data scientist, you need to help user to achieve their goal [This is a titanic passenger survival dataset, your goal is to predict passenger survival outcome. The target column is Survived. Perform data analysis, data preprocessing, feature engineering, and modeling to predict the target. Report accuracy on the eval data. Train data path: 'tests/data/ml_datasets/titanic/split_train.csv', eval data path: 'tests/data/ml_datasets/titanic/split_eval.csv'.] step-by-step in an continuous Jupyter notebook.\\n\\n## Done Tasks\\n```python\\nimport pandas as pd\\ntrain_data = pd.read_csv('tests/data/ml_datasets/titanic/split_train.csv')\\ndisplay(train_data.head())\\ntrain_data.info()\\ntrain_data.describe()\\ntrain_data.describe(include=['O'])\\nmissing_values = train_data.isnull().sum()\\nmissing_values[missing_values > 0]\\nimport seaborn as sns\\nimport matplotlib.pyplot as plt\\nsns.countplot(x='Survived', data=train_data)\\nplt.title('Distribution of Survival')\\nplt.show()\\nsns.barplot(x='Sex', y='Survived', data=train_data)\\nplt.title('Survival Rate by Sex')\\nplt.show()\\nsns.barplot(x='Pclass', y='Survived', data=train_data)\\nplt.title('Survival Rate by Class')\\nplt.show()\\nsns.barplot(x='Embarked', y='Survived', data=train_data)\\nplt.title('Survival Rate by Embarkation Port')\\nplt.show()\\n```end\\n\\n## Current Task\\nPreprocess the data by handling missing values, encoding categorical variables, and scaling features.\\n\\n# Latest Data Info\\nLatest data info after previous tasks:\\ncolumn_info\\n{'Category': ['Name', 'Sex', 'Ticket', 'Cabin', 'Embarked'], 'Numeric': ['PassengerId', 'Survived', 'Pclass', 'Age', 'SibSp', 'Parch', 'Fare'], 'Datetime': [], 'Others': []}\\n\\n\\n# Task\\nWrite complete code for 'Current Task'. And avoid duplicating code from 'Done Tasks', such as repeated import of packages, reading data, etc.\\nSpecifically, \\nThe current task is about data preprocessing, please note the following:\\n- Monitor data types per column, applying appropriate methods.\\n- Ensure operations are on existing dataset columns.\\n- Avoid writing processed data to files.\\n- Avoid any change to label column, such as standardization, etc.\\n- Prefer alternatives to one-hot encoding for categorical data.\\n- Only encode or scale necessary columns to allow for potential feature-specific engineering tasks (like time_extract, binning, extraction, etc.) later.\\n- Each step do data preprocessing to train, must do same for test separately at the same time.\\n\\n\\n# Code Steps:\\nStrictly follow steps below when you writing code if it's convenient.\\n\\n\\n# Capabilities\\n- You can utilize pre-defined tools in any code lines from 'Available Tools' in the form of Python Class.\\n- You can freely combine the use of any other public packages, like sklearn, numpy, pandas, etc..\\n\\n# Available Tools:\\nEach Class tool is described in JSON format. When you call a tool, import the tool from its path first.\\n{'FillMissingValue': {'type': 'class', 'description': 'Completing missing values with simple strategies', 'methods': {'__init__': {'description': 'Initialize self.', 'parameters': {'properties': {'features': {'type': 'list', 'description': 'columns to be processed'}, 'strategy': {'type': 'str', 'description': 'the imputation strategy, notice mean/median can only be used for numeric features', 'default': 'mean', 'enum': ['mean', 'median', 'most_frequent', 'constant']}, 'fill_value': {'type': 'int', 'description': 'fill_value is used to replace all occurrences of missing_values', 'default': None}}, 'required': ['features']}}, 'fit': {'description': 'Fit the FillMissingValue model.', 'parameters': {'properties': {'df': {'type': 'DataFrame', 'description': 'The input DataFrame.'}}, 'required': ['df']}}, 'transform': {'description': 'Transform the input DataFrame with the fitted model.', 'parameters': {'properties': {'df': {'type': 'DataFrame', 'description': 'The input DataFrame.'}}, 'required': ['df']}, 'returns': {'df': {'type': 'DataFrame', 'description': 'The transformed DataFrame.'}}}, 'fit_transform': {'description': 'Fit and transform the input DataFrame.', 'parameters': {'properties': {'df': {'type': 'DataFrame', 'description': 'The input DataFrame.'}}, 'required': ['df']}, 'returns': {'df': {'type': 'DataFrame', 'description': 'The transformed DataFrame.'}}}}, 'tool_path': 'metagpt/tools/libs/data_preprocess.py'}}\\n\\n# Output Example:\\nwhen current task is \\\"do data preprocess, like fill missing value, handle outliers, etc.\\\", and their are two steps in 'Code Steps', the code be like:\\n```python\\n# Step 1: fill missing value\\n# Tools used: ['FillMissingValue']\\nfrom metagpt.tools.libs.data_preprocess import FillMissingValue\\n\\ntrain_processed = train.copy()\\ntest_processed = test.copy()\\nnum_cols = train_processed.select_dtypes(include='number').columns.tolist()\\nif 'label' in num_cols:\\n num_cols.remove('label')\\nfill_missing_value = FillMissingValue(features=num_cols, strategy='mean')\\nfill_missing_value.fit(train_processed)\\ntrain_processed = fill_missing_value.transform(train_processed)\\ntest_processed = fill_missing_value.transform(test_processed)\\n\\n# Step 2: handle outliers\\nfor col in num_cols:\\n low, high = train_processed[col].quantile([0.01, 0.99])\\n train_processed[col] = train_processed[col].clip(low, high)\\n test_processed[col] = test_processed[col].clip(low, high)\\n```end\\n\\n# Constraints:\\n- Ensure the output new code is executable in the same Jupyter notebook with previous tasks code have been executed.\\n- Always prioritize using pre-defined tools for the same functionality.\\n- Always copy the DataFrame before processing it and use the copy to process.\\n- The output code should contain all steps implemented correctly in 'Code Steps'.\\n\"}]": {
+ "code": "# Step 1: Handle missing values\nfrom metagpt.tools.libs.data_preprocess import FillMissingValue\n\n# Create copies of the datasets to avoid changing the original data\ntrain_processed = train_data.copy()\neval_data = pd.read_csv('tests/data/ml_datasets/titanic/split_eval.csv')\n\n# Fill missing values for 'Age' with median and 'Embarked' with most frequent value\nfill_missing_age = FillMissingValue(features=['Age'], strategy='median')\ntrain_processed = fill_missing_age.fit_transform(train_processed)\neval_processed = fill_missing_age.transform(eval_data)\n\nfill_missing_embarked = FillMissingValue(features=['Embarked'], strategy='most_frequent')\ntrain_processed = fill_missing_embarked.fit_transform(train_processed)\neval_processed = fill_missing_embarked.transform(eval_data)\n\n# Since 'Cabin' has a lot of missing values, we'll create a binary feature indicating whether cabin info is missing\ntrain_processed['Cabin_Ind'] = train_processed['Cabin'].isnull().astype(int)\neval_processed['Cabin_Ind'] = eval_processed['Cabin'].isnull().astype(int)\n\n# Step 2: Encode categorical variables\nfrom sklearn.preprocessing import LabelEncoder\n\nlabel_encoder = LabelEncoder()\n\n# Encode 'Sex'\ntrain_processed['Sex'] = label_encoder.fit_transform(train_processed['Sex'])\neval_processed['Sex'] = label_encoder.transform(eval_processed['Sex'])\n\n# Encode 'Embarked'\ntrain_processed['Embarked'] = label_encoder.fit_transform(train_processed['Embarked'])\neval_processed['Embarked'] = label_encoder.transform(eval_processed['Embarked'])\n\n# Step 3: Scale features\nfrom sklearn.preprocessing import StandardScaler\n\nscaler = StandardScaler()\n\n# Scale 'Age' and 'Fare'\nfor feature in ['Age', 'Fare']:\n train_processed[[feature]] = scaler.fit_transform(train_processed[[feature]])\n eval_processed[[feature]] = scaler.transform(eval_processed[[feature]])\n\n# Display the first few rows of the processed train dataset\ndisplay(train_processed.head())"
+ },
+ "[{\"role\": \"user\", \"content\": \"\\n# Background\\nKeep dataset column information updated before model train.\\n## Done Tasks\\n```python\\nimport pandas as pd\\ntrain_data = pd.read_csv('tests/data/ml_datasets/titanic/split_train.csv')\\ndisplay(train_data.head())\\ntrain_data.info()\\ntrain_data.describe()\\ntrain_data.describe(include=['O'])\\nmissing_values = train_data.isnull().sum()\\nmissing_values[missing_values > 0]\\nimport seaborn as sns\\nimport matplotlib.pyplot as plt\\nsns.countplot(x='Survived', data=train_data)\\nplt.title('Distribution of Survival')\\nplt.show()\\nsns.barplot(x='Sex', y='Survived', data=train_data)\\nplt.title('Survival Rate by Sex')\\nplt.show()\\nsns.barplot(x='Pclass', y='Survived', data=train_data)\\nplt.title('Survival Rate by Class')\\nplt.show()\\nsns.barplot(x='Embarked', y='Survived', data=train_data)\\nplt.title('Survival Rate by Embarkation Port')\\nplt.show()\\n\\nfrom metagpt.tools.libs.data_preprocess import FillMissingValue\\ntrain_processed = train_data.copy()\\neval_data = pd.read_csv('tests/data/ml_datasets/titanic/split_eval.csv')\\nfill_missing_age = FillMissingValue(features=['Age'], strategy='median')\\ntrain_processed = fill_missing_age.fit_transform(train_processed)\\neval_processed = fill_missing_age.transform(eval_data)\\nfill_missing_embarked = FillMissingValue(features=['Embarked'], strategy='most_frequent')\\ntrain_processed = fill_missing_embarked.fit_transform(train_processed)\\neval_processed = fill_missing_embarked.transform(eval_data)\\ntrain_processed['Cabin_Ind'] = train_processed['Cabin'].isnull().astype(int)\\neval_processed['Cabin_Ind'] = eval_processed['Cabin'].isnull().astype(int)\\nfrom sklearn.preprocessing import LabelEncoder\\nlabel_encoder = LabelEncoder()\\ntrain_processed['Sex'] = label_encoder.fit_transform(train_processed['Sex'])\\neval_processed['Sex'] = label_encoder.transform(eval_processed['Sex'])\\ntrain_processed['Embarked'] = label_encoder.fit_transform(train_processed['Embarked'])\\neval_processed['Embarked'] = label_encoder.transform(eval_processed['Embarked'])\\nfrom sklearn.preprocessing import StandardScaler\\nscaler = StandardScaler()\\nfor feature in ['Age', 'Fare']:\\n train_processed[[feature]] = scaler.fit_transform(train_processed[[feature]])\\n eval_processed[[feature]] = scaler.transform(eval_processed[[feature]])\\ndisplay(train_processed.head())\\n```end\\n\\n# Task\\nUpdate and print the dataset's column information only if the train or test data has changed. Use the following code:\\n```python\\nfrom metagpt.tools.libs.data_preprocess import get_column_info\\n\\ncolumn_info = get_column_info(df)\\nprint(\\\"column_info\\\")\\nprint(column_info)\\n```end\\n\\n# Constraints:\\n- Use the DataFrame variable from 'Done Tasks' in place of df.\\n- Import `get_column_info` only if it's not already imported.\\n\"}]": {
+ "code": "from metagpt.tools.libs.data_preprocess import get_column_info\ncolumn_info = get_column_info(train_processed)\nprint(\"column_info\")\nprint(column_info)"
+ },
+ "[{\"role\": \"user\", \"content\": \"\\n## User Requirement:\\nConduct feature engineering to create new features that may help improve model performance.\\n\\n## Task\\nRecommend up to five tools from 'Available Tools' that can help solve the 'User Requirement'. \\nThis is a detailed code steps for current task. You can refer to it when recommending tools.\\n\\n\\n## Available Tools:\\n{'CatCross': 'Add pairwise crossed features and convert them to numerical features.'}\\n\\n## Tool Selection and Instructions:\\n- Select tools most relevant to completing the 'User Requirement'.\\n- If you believe that no tools are suitable, indicate with an empty list.\\n- Only list the names of the tools, not the full schema of each tool.\\n- Ensure selected tools are listed in 'Available Tools'.\\n\"}]": {
+ "recommend_tools": [
+ "CatCross"
+ ]
+ },
+ "[{\"role\": \"user\", \"content\": \"\\n# Background\\nAs a data scientist, you need to help user to achieve their goal [This is a titanic passenger survival dataset, your goal is to predict passenger survival outcome. The target column is Survived. Perform data analysis, data preprocessing, feature engineering, and modeling to predict the target. Report accuracy on the eval data. Train data path: 'tests/data/ml_datasets/titanic/split_train.csv', eval data path: 'tests/data/ml_datasets/titanic/split_eval.csv'.] step-by-step in an continuous Jupyter notebook.\\n\\n## Done Tasks\\n```python\\nimport pandas as pd\\ntrain_data = pd.read_csv('tests/data/ml_datasets/titanic/split_train.csv')\\ndisplay(train_data.head())\\ntrain_data.info()\\ntrain_data.describe()\\ntrain_data.describe(include=['O'])\\nmissing_values = train_data.isnull().sum()\\nmissing_values[missing_values > 0]\\nimport seaborn as sns\\nimport matplotlib.pyplot as plt\\nsns.countplot(x='Survived', data=train_data)\\nplt.title('Distribution of Survival')\\nplt.show()\\nsns.barplot(x='Sex', y='Survived', data=train_data)\\nplt.title('Survival Rate by Sex')\\nplt.show()\\nsns.barplot(x='Pclass', y='Survived', data=train_data)\\nplt.title('Survival Rate by Class')\\nplt.show()\\nsns.barplot(x='Embarked', y='Survived', data=train_data)\\nplt.title('Survival Rate by Embarkation Port')\\nplt.show()\\n\\nfrom metagpt.tools.libs.data_preprocess import FillMissingValue\\ntrain_processed = train_data.copy()\\neval_data = pd.read_csv('tests/data/ml_datasets/titanic/split_eval.csv')\\nfill_missing_age = FillMissingValue(features=['Age'], strategy='median')\\ntrain_processed = fill_missing_age.fit_transform(train_processed)\\neval_processed = fill_missing_age.transform(eval_data)\\nfill_missing_embarked = FillMissingValue(features=['Embarked'], strategy='most_frequent')\\ntrain_processed = fill_missing_embarked.fit_transform(train_processed)\\neval_processed = fill_missing_embarked.transform(eval_data)\\ntrain_processed['Cabin_Ind'] = train_processed['Cabin'].isnull().astype(int)\\neval_processed['Cabin_Ind'] = eval_processed['Cabin'].isnull().astype(int)\\nfrom sklearn.preprocessing import LabelEncoder\\nlabel_encoder = LabelEncoder()\\ntrain_processed['Sex'] = label_encoder.fit_transform(train_processed['Sex'])\\neval_processed['Sex'] = label_encoder.transform(eval_processed['Sex'])\\ntrain_processed['Embarked'] = label_encoder.fit_transform(train_processed['Embarked'])\\neval_processed['Embarked'] = label_encoder.transform(eval_processed['Embarked'])\\nfrom sklearn.preprocessing import StandardScaler\\nscaler = StandardScaler()\\nfor feature in ['Age', 'Fare']:\\n train_processed[[feature]] = scaler.fit_transform(train_processed[[feature]])\\n eval_processed[[feature]] = scaler.transform(eval_processed[[feature]])\\ndisplay(train_processed.head())\\n```end\\n\\n## Current Task\\nConduct feature engineering to create new features that may help improve model performance.\\n\\n# Latest Data Info\\nLatest data info after previous tasks:\\ncolumn_info\\n{'Category': ['Name', 'Ticket', 'Cabin'], 'Numeric': ['PassengerId', 'Survived', 'Pclass', 'Sex', 'Age', 'SibSp', 'Parch', 'Fare', 'Embarked', 'Cabin_Ind'], 'Datetime': [], 'Others': []}\\n\\n\\n# Task\\nWrite complete code for 'Current Task'. And avoid duplicating code from 'Done Tasks', such as repeated import of packages, reading data, etc.\\nSpecifically, \\nThe current task is about feature engineering. when performing it, please adhere to the following principles:\\n- Generate as diverse features as possible to improve the model's performance step-by-step. \\n- If potential impactful features are not included in 'Code Steps', add new steps to generate them.\\n- Avoid creating redundant or excessively numerous features in one step.\\n- Exclude ID columns from feature generation and remove them.\\n- Each step do feature engineering to train, must do same for test separately at the same time.\\n- Avoid using the label column to create features, except for cat encoding.\\n- Use the data from previous task result if exist, do not mock or reload data yourself.\\n\\n\\n# Code Steps:\\nStrictly follow steps below when you writing code if it's convenient.\\n\\n\\n# Capabilities\\n- You can utilize pre-defined tools in any code lines from 'Available Tools' in the form of Python Class.\\n- You can freely combine the use of any other public packages, like sklearn, numpy, pandas, etc..\\n\\n# Available Tools:\\nEach Class tool is described in JSON format. When you call a tool, import the tool from its path first.\\n{'CatCross': {'type': 'class', 'description': 'Add pairwise crossed features and convert them to numerical features.', 'methods': {'__init__': {'description': 'Initialize self.', 'parameters': {'properties': {'cols': {'type': 'list', 'description': 'Columns to be pairwise crossed, at least 2 columns.'}, 'max_cat_num': {'type': 'int', 'description': 'Maximum unique categories per crossed feature.', 'default': 100}}}, 'required': ['cols']}, 'fit': {'description': 'Fit the CatCross model.', 'parameters': {'properties': {'df': {'type': 'DataFrame', 'description': 'The input DataFrame.'}}, 'required': ['df']}}, 'transform': {'description': 'Transform the input DataFrame with the fitted model.', 'parameters': {'properties': {'df': {'type': 'DataFrame', 'description': 'The input DataFrame.'}}, 'required': ['df']}, 'returns': {'df': {'type': 'DataFrame', 'description': 'The transformed DataFrame.'}}}, 'fit_transform': {'description': 'Fit and transform the input DataFrame.', 'parameters': {'properties': {'df': {'type': 'DataFrame', 'description': 'The input DataFrame.'}}, 'required': ['df']}, 'returns': {'df': {'type': 'DataFrame', 'description': 'The transformed DataFrame.'}}}}, 'tool_path': 'metagpt/tools/libs/feature_engineering.py'}}\\n\\n# Output Example:\\nwhen current task is \\\"do data preprocess, like fill missing value, handle outliers, etc.\\\", and their are two steps in 'Code Steps', the code be like:\\n```python\\n# Step 1: fill missing value\\n# Tools used: ['FillMissingValue']\\nfrom metagpt.tools.libs.data_preprocess import FillMissingValue\\n\\ntrain_processed = train.copy()\\ntest_processed = test.copy()\\nnum_cols = train_processed.select_dtypes(include='number').columns.tolist()\\nif 'label' in num_cols:\\n num_cols.remove('label')\\nfill_missing_value = FillMissingValue(features=num_cols, strategy='mean')\\nfill_missing_value.fit(train_processed)\\ntrain_processed = fill_missing_value.transform(train_processed)\\ntest_processed = fill_missing_value.transform(test_processed)\\n\\n# Step 2: handle outliers\\nfor col in num_cols:\\n low, high = train_processed[col].quantile([0.01, 0.99])\\n train_processed[col] = train_processed[col].clip(low, high)\\n test_processed[col] = test_processed[col].clip(low, high)\\n```end\\n\\n# Constraints:\\n- Ensure the output new code is executable in the same Jupyter notebook with previous tasks code have been executed.\\n- Always prioritize using pre-defined tools for the same functionality.\\n- Always copy the DataFrame before processing it and use the copy to process.\\n- The output code should contain all steps implemented correctly in 'Code Steps'.\\n\"}]": {
+ "code": "# Step 1: Create a feature for the number of family members on board\ntrain_processed['Family_Size'] = train_processed['SibSp'] + train_processed['Parch']\neval_processed['Family_Size'] = eval_processed['SibSp'] + eval_processed['Parch']\n\n# Step 2: Create a feature to indicate whether a passenger was alone or not\ntrain_processed['Is_Alone'] = (train_processed['Family_Size'] == 0).astype(int)\neval_processed['Is_Alone'] = (eval_processed['Family_Size'] == 0).astype(int)\n\n# Step 3: Create a feature for the title extracted from the passenger's name\ntrain_processed['Title'] = train_processed['Name'].str.extract(' ([A-Za-z]+)\\.', expand=False)\neval_processed['Title'] = eval_processed['Name'].str.extract(' ([A-Za-z]+)\\.', expand=False)\n\n# Step 4: Create a feature for the deck extracted from the cabin number\ntrain_processed['Deck'] = train_processed['Cabin'].str[0]\neval_processed['Deck'] = eval_processed['Cabin'].str[0]\n\n# Step 5: Create a feature for the ticket class and sex cross\n# Tools used: ['CatCross']\nfrom metagpt.tools.libs.feature_engineering import CatCross\n\n# Initialize CatCross with the desired columns\ncat_cross = CatCross(cols=['Pclass', 'Sex'])\n\n# Fit and transform the train data\ntrain_processed = cat_cross.fit_transform(train_processed)\n\n# Transform the eval data\neval_processed = cat_cross.transform(eval_processed)\n\n# Remove unnecessary columns\nfor df in [train_processed, eval_processed]:\n df.drop(['PassengerId', 'Name', 'Ticket', 'Cabin'], axis=1, inplace=True)"
+ },
+ "[{\"role\": \"user\", \"content\": \"\\n# Background\\nKeep dataset column information updated before model train.\\n## Done Tasks\\n```python\\nimport pandas as pd\\ntrain_data = pd.read_csv('tests/data/ml_datasets/titanic/split_train.csv')\\ndisplay(train_data.head())\\ntrain_data.info()\\ntrain_data.describe()\\ntrain_data.describe(include=['O'])\\nmissing_values = train_data.isnull().sum()\\nmissing_values[missing_values > 0]\\nimport seaborn as sns\\nimport matplotlib.pyplot as plt\\nsns.countplot(x='Survived', data=train_data)\\nplt.title('Distribution of Survival')\\nplt.show()\\nsns.barplot(x='Sex', y='Survived', data=train_data)\\nplt.title('Survival Rate by Sex')\\nplt.show()\\nsns.barplot(x='Pclass', y='Survived', data=train_data)\\nplt.title('Survival Rate by Class')\\nplt.show()\\nsns.barplot(x='Embarked', y='Survived', data=train_data)\\nplt.title('Survival Rate by Embarkation Port')\\nplt.show()\\n\\nfrom metagpt.tools.libs.data_preprocess import FillMissingValue\\ntrain_processed = train_data.copy()\\neval_data = pd.read_csv('tests/data/ml_datasets/titanic/split_eval.csv')\\nfill_missing_age = FillMissingValue(features=['Age'], strategy='median')\\ntrain_processed = fill_missing_age.fit_transform(train_processed)\\neval_processed = fill_missing_age.transform(eval_data)\\nfill_missing_embarked = FillMissingValue(features=['Embarked'], strategy='most_frequent')\\ntrain_processed = fill_missing_embarked.fit_transform(train_processed)\\neval_processed = fill_missing_embarked.transform(eval_data)\\ntrain_processed['Cabin_Ind'] = train_processed['Cabin'].isnull().astype(int)\\neval_processed['Cabin_Ind'] = eval_processed['Cabin'].isnull().astype(int)\\nfrom sklearn.preprocessing import LabelEncoder\\nlabel_encoder = LabelEncoder()\\ntrain_processed['Sex'] = label_encoder.fit_transform(train_processed['Sex'])\\neval_processed['Sex'] = label_encoder.transform(eval_processed['Sex'])\\ntrain_processed['Embarked'] = label_encoder.fit_transform(train_processed['Embarked'])\\neval_processed['Embarked'] = label_encoder.transform(eval_processed['Embarked'])\\nfrom sklearn.preprocessing import StandardScaler\\nscaler = StandardScaler()\\nfor feature in ['Age', 'Fare']:\\n train_processed[[feature]] = scaler.fit_transform(train_processed[[feature]])\\n eval_processed[[feature]] = scaler.transform(eval_processed[[feature]])\\ndisplay(train_processed.head())\\n\\ntrain_processed['Family_Size'] = train_processed['SibSp'] + train_processed['Parch']\\neval_processed['Family_Size'] = eval_processed['SibSp'] + eval_processed['Parch']\\ntrain_processed['Is_Alone'] = (train_processed['Family_Size'] == 0).astype(int)\\neval_processed['Is_Alone'] = (eval_processed['Family_Size'] == 0).astype(int)\\ntrain_processed['Title'] = train_processed['Name'].str.extract(' ([A-Za-z]+)\\\\.', expand=False)\\neval_processed['Title'] = eval_processed['Name'].str.extract(' ([A-Za-z]+)\\\\.', expand=False)\\ntrain_processed['Deck'] = train_processed['Cabin'].str[0]\\neval_processed['Deck'] = eval_processed['Cabin'].str[0]\\nfrom metagpt.tools.libs.feature_engineering import CatCross\\ncat_cross = CatCross(cols=['Pclass', 'Sex'])\\ntrain_processed = cat_cross.fit_transform(train_processed)\\neval_processed = cat_cross.transform(eval_processed)\\nfor df in [train_processed, eval_processed]:\\n df.drop(['PassengerId', 'Name', 'Ticket', 'Cabin'], axis=1, inplace=True)\\n```end\\n\\n# Task\\nUpdate and print the dataset's column information only if the train or test data has changed. Use the following code:\\n```python\\nfrom metagpt.tools.libs.data_preprocess import get_column_info\\n\\ncolumn_info = get_column_info(df)\\nprint(\\\"column_info\\\")\\nprint(column_info)\\n```end\\n\\n# Constraints:\\n- Use the DataFrame variable from 'Done Tasks' in place of df.\\n- Import `get_column_info` only if it's not already imported.\\n\"}]": {
+ "code": "from metagpt.tools.libs.data_preprocess import get_column_info\ncolumn_info = get_column_info(train_processed)\nprint(\"column_info\")\nprint(column_info)"
+ },
+ "[{\"role\": \"user\", \"content\": \"\\n# Background\\nAs a data scientist, you need to help user to achieve their goal [This is a titanic passenger survival dataset, your goal is to predict passenger survival outcome. The target column is Survived. Perform data analysis, data preprocessing, feature engineering, and modeling to predict the target. Report accuracy on the eval data. Train data path: 'tests/data/ml_datasets/titanic/split_train.csv', eval data path: 'tests/data/ml_datasets/titanic/split_eval.csv'.] step-by-step in an continuous Jupyter notebook.\\n\\n## Done Tasks\\n```python\\nimport pandas as pd\\ntrain_data = pd.read_csv('tests/data/ml_datasets/titanic/split_train.csv')\\ndisplay(train_data.head())\\ntrain_data.info()\\ntrain_data.describe()\\ntrain_data.describe(include=['O'])\\nmissing_values = train_data.isnull().sum()\\nmissing_values[missing_values > 0]\\nimport seaborn as sns\\nimport matplotlib.pyplot as plt\\nsns.countplot(x='Survived', data=train_data)\\nplt.title('Distribution of Survival')\\nplt.show()\\nsns.barplot(x='Sex', y='Survived', data=train_data)\\nplt.title('Survival Rate by Sex')\\nplt.show()\\nsns.barplot(x='Pclass', y='Survived', data=train_data)\\nplt.title('Survival Rate by Class')\\nplt.show()\\nsns.barplot(x='Embarked', y='Survived', data=train_data)\\nplt.title('Survival Rate by Embarkation Port')\\nplt.show()\\n\\nfrom metagpt.tools.libs.data_preprocess import FillMissingValue\\ntrain_processed = train_data.copy()\\neval_data = pd.read_csv('tests/data/ml_datasets/titanic/split_eval.csv')\\nfill_missing_age = FillMissingValue(features=['Age'], strategy='median')\\ntrain_processed = fill_missing_age.fit_transform(train_processed)\\neval_processed = fill_missing_age.transform(eval_data)\\nfill_missing_embarked = FillMissingValue(features=['Embarked'], strategy='most_frequent')\\ntrain_processed = fill_missing_embarked.fit_transform(train_processed)\\neval_processed = fill_missing_embarked.transform(eval_data)\\ntrain_processed['Cabin_Ind'] = train_processed['Cabin'].isnull().astype(int)\\neval_processed['Cabin_Ind'] = eval_processed['Cabin'].isnull().astype(int)\\nfrom sklearn.preprocessing import LabelEncoder\\nlabel_encoder = LabelEncoder()\\ntrain_processed['Sex'] = label_encoder.fit_transform(train_processed['Sex'])\\neval_processed['Sex'] = label_encoder.transform(eval_processed['Sex'])\\ntrain_processed['Embarked'] = label_encoder.fit_transform(train_processed['Embarked'])\\neval_processed['Embarked'] = label_encoder.transform(eval_processed['Embarked'])\\nfrom sklearn.preprocessing import StandardScaler\\nscaler = StandardScaler()\\nfor feature in ['Age', 'Fare']:\\n train_processed[[feature]] = scaler.fit_transform(train_processed[[feature]])\\n eval_processed[[feature]] = scaler.transform(eval_processed[[feature]])\\ndisplay(train_processed.head())\\n\\ntrain_processed['Family_Size'] = train_processed['SibSp'] + train_processed['Parch']\\neval_processed['Family_Size'] = eval_processed['SibSp'] + eval_processed['Parch']\\ntrain_processed['Is_Alone'] = (train_processed['Family_Size'] == 0).astype(int)\\neval_processed['Is_Alone'] = (eval_processed['Family_Size'] == 0).astype(int)\\ntrain_processed['Title'] = train_processed['Name'].str.extract(' ([A-Za-z]+)\\\\.', expand=False)\\neval_processed['Title'] = eval_processed['Name'].str.extract(' ([A-Za-z]+)\\\\.', expand=False)\\ntrain_processed['Deck'] = train_processed['Cabin'].str[0]\\neval_processed['Deck'] = eval_processed['Cabin'].str[0]\\nfrom metagpt.tools.libs.feature_engineering import CatCross\\ncat_cross = CatCross(cols=['Pclass', 'Sex'])\\ntrain_processed = cat_cross.fit_transform(train_processed)\\neval_processed = cat_cross.transform(eval_processed)\\nfor df in [train_processed, eval_processed]:\\n df.drop(['PassengerId', 'Name', 'Ticket', 'Cabin'], axis=1, inplace=True)\\n```end\\n\\n## Current Task\\nSelect and train a machine learning model using the processed train dataset.\\n\\n# Latest Data Info\\nLatest data info after previous tasks:\\ncolumn_info\\n{'Category': ['Title', 'Deck'], 'Numeric': ['Survived', 'Pclass', 'Sex', 'Age', 'SibSp', 'Parch', 'Fare', 'Embarked', 'Cabin_Ind', 'Family_Size', 'Is_Alone', 'Pclass_Sex'], 'Datetime': [], 'Others': []}\\n\\n\\n# Task\\nWrite complete code for 'Current Task'. And avoid duplicating code from 'Done Tasks', such as repeated import of packages, reading data, etc.\\nSpecifically, \\nThe current task is about training a model, please ensure high performance:\\n- Keep in mind that your user prioritizes results and is highly focused on model performance. So, when needed, feel free to use models of any complexity to improve effectiveness, such as XGBoost, CatBoost, etc.\\n- If non-numeric columns exist, perform label encode together with all steps.\\n- Use the data from previous task result directly, do not mock or reload data yourself.\\n- Set suitable hyperparameters for the model, make metrics as high as possible.\\n\\n\\n# Code Steps:\\nStrictly follow steps below when you writing code if it's convenient.\\n\\n\\n# Output Example:\\nwhen current task is \\\"train a lightgbm model on training data\\\", and their are two steps in 'Code Steps', the code be like:\\n```python\\n# Step 1: check data type and convert to numeric\\nojb_cols = train.select_dtypes(include='object').columns.tolist()\\n\\nfor col in obj_cols:\\n encoder = LabelEncoder()\\n train[col] = encoder.fit_transform(train[col].unique().tolist() + ['unknown'])\\n test[col] = test[col].apply(lambda x: x if x in encoder.classes_ else 'unknown')\\n test[col] = encoder.transform(test[col])\\n\\n# Step 2: train lightgbm model\\nmodel = LGBMClassifier()\\nmodel.fit(train, y_train)\\n```end\\n\\n# Constraints:\\n- Ensure the output new code is executable in the same Jupyter notebook with previous tasks code have been executed.\\n- The output code should contain all steps implemented in 'Code Steps'.\\n\"}]": {
+ "code": "from sklearn.model_selection import train_test_split\nfrom sklearn.metrics import accuracy_score\nfrom xgboost import XGBClassifier\n\n# Step 1: Prepare the data\n# Separate features and target variable\nX = train_processed.drop('Survived', axis=1)\ny = train_processed['Survived']\n\n# Step 2: Split the data into training and validation sets\nX_train, X_val, y_train, y_val = train_test_split(X, y, test_size=0.2, random_state=42)\n\n# Step 3: Initialize and train the XGBoost model\n# Note: Hyperparameters are set to values that generally perform well, but further tuning could be done.\nmodel = XGBClassifier(n_estimators=100, learning_rate=0.05, max_depth=4, subsample=0.8, colsample_bytree=0.8, random_state=42)\nmodel.fit(X_train, y_train)\n\n# Step 4: Evaluate the model\ny_pred = model.predict(X_val)\naccuracy = accuracy_score(y_val, y_pred)\nprint(f'Validation Accuracy: {accuracy:.4f}')"
+ },
+ "[{\"role\": \"system\", \"content\": \"You are an AI Python assistant. You will be given your previous implementation code of a task, runtime error results, and a hint to change the implementation appropriately. Write your full implementation \"}, {\"role\": \"user\", \"content\": \"\\nHere is an example for you.\\n\\nExample 1:\\n[previous impl]:\\n```python\\ndef add(a: int, b: int) -> int:\\n \\\"\\\"\\\"\\n Given integers a and b, return the total value of a and b.\\n \\\"\\\"\\\"\\n return a - b\\n```\\n\\n[runtime Error]:\\nTested passed:\\n\\nTests failed:\\nassert add(1, 2) == 3 # output: -1\\nassert add(1, 2) == 4 # output: -1\\n\\n[reflection on previous impl]:\\nThe implementation failed the test cases where the input integers are 1 and 2. The issue arises because the code does not add the two integers together, but instead subtracts the second integer from the first. To fix this issue, we should change the operator from `-` to `+` in the return statement. This will ensure that the function returns the correct output for the given input.\\n\\n[improved impl]:\\n```python\\ndef add(a: int, b: int) -> int:\\n \\\"\\\"\\\"\\n Given integers a and b, return the total value of a and b.\\n \\\"\\\"\\\"\\n return a + b\\n```\\n\\n[context]\\n[user: \\n# Background\\nAs a data scientist, you need to help user to achieve their goal [This is a titanic passenger survival dataset, your goal is to predict passenger survival outcome. The target column is Survived. Perform data analysis, data preprocessing, feature engineering, and modeling to predict the target. Report accuracy on the eval data. Train data path: 'tests/data/ml_datasets/titanic/split_train.csv', eval data path: 'tests/data/ml_datasets/titanic/split_eval.csv'.] step-by-step in an continuous Jupyter notebook.\\n\\n## Done Tasks\\n```python\\nimport pandas as pd\\ntrain_data = pd.read_csv('tests/data/ml_datasets/titanic/split_train.csv')\\ndisplay(train_data.head())\\ntrain_data.info()\\ntrain_data.describe()\\ntrain_data.describe(include=['O'])\\nmissing_values = train_data.isnull().sum()\\nmissing_values[missing_values > 0]\\nimport seaborn as sns\\nimport matplotlib.pyplot as plt\\nsns.countplot(x='Survived', data=train_data)\\nplt.title('Distribution of Survival')\\nplt.show()\\nsns.barplot(x='Sex', y='Survived', data=train_data)\\nplt.title('Survival Rate by Sex')\\nplt.show()\\nsns.barplot(x='Pclass', y='Survived', data=train_data)\\nplt.title('Survival Rate by Class')\\nplt.show()\\nsns.barplot(x='Embarked', y='Survived', data=train_data)\\nplt.title('Survival Rate by Embarkation Port')\\nplt.show()\\n\\nfrom metagpt.tools.libs.data_preprocess import FillMissingValue\\ntrain_processed = train_data.copy()\\neval_data = pd.read_csv('tests/data/ml_datasets/titanic/split_eval.csv')\\nfill_missing_age = FillMissingValue(features=['Age'], strategy='median')\\ntrain_processed = fill_missing_age.fit_transform(train_processed)\\neval_processed = fill_missing_age.transform(eval_data)\\nfill_missing_embarked = FillMissingValue(features=['Embarked'], strategy='most_frequent')\\ntrain_processed = fill_missing_embarked.fit_transform(train_processed)\\neval_processed = fill_missing_embarked.transform(eval_data)\\ntrain_processed['Cabin_Ind'] = train_processed['Cabin'].isnull().astype(int)\\neval_processed['Cabin_Ind'] = eval_processed['Cabin'].isnull().astype(int)\\nfrom sklearn.preprocessing import LabelEncoder\\nlabel_encoder = LabelEncoder()\\ntrain_processed['Sex'] = label_encoder.fit_transform(train_processed['Sex'])\\neval_processed['Sex'] = label_encoder.transform(eval_processed['Sex'])\\ntrain_processed['Embarked'] = label_encoder.fit_transform(train_processed['Embarked'])\\neval_processed['Embarked'] = label_encoder.transform(eval_processed['Embarked'])\\nfrom sklearn.preprocessing import StandardScaler\\nscaler = StandardScaler()\\nfor feature in ['Age', 'Fare']:\\n train_processed[[feature]] = scaler.fit_transform(train_processed[[feature]])\\n eval_processed[[feature]] = scaler.transform(eval_processed[[feature]])\\ndisplay(train_processed.head())\\n\\ntrain_processed['Family_Size'] = train_processed['SibSp'] + train_processed['Parch']\\neval_processed['Family_Size'] = eval_processed['SibSp'] + eval_processed['Parch']\\ntrain_processed['Is_Alone'] = (train_processed['Family_Size'] == 0).astype(int)\\neval_processed['Is_Alone'] = (eval_processed['Family_Size'] == 0).astype(int)\\ntrain_processed['Title'] = train_processed['Name'].str.extract(' ([A-Za-z]+)\\\\.', expand=False)\\neval_processed['Title'] = eval_processed['Name'].str.extract(' ([A-Za-z]+)\\\\.', expand=False)\\ntrain_processed['Deck'] = train_processed['Cabin'].str[0]\\neval_processed['Deck'] = eval_processed['Cabin'].str[0]\\nfrom metagpt.tools.libs.feature_engineering import CatCross\\ncat_cross = CatCross(cols=['Pclass', 'Sex'])\\ntrain_processed = cat_cross.fit_transform(train_processed)\\neval_processed = cat_cross.transform(eval_processed)\\nfor df in [train_processed, eval_processed]:\\n df.drop(['PassengerId', 'Name', 'Ticket', 'Cabin'], axis=1, inplace=True)\\n```end\\n\\n## Current Task\\nSelect and train a machine learning model using the processed train dataset.\\n\\n# Latest Data Info\\nLatest data info after previous tasks:\\ncolumn_info\\n{'Category': ['Title', 'Deck'], 'Numeric': ['Survived', 'Pclass', 'Sex', 'Age', 'SibSp', 'Parch', 'Fare', 'Embarked', 'Cabin_Ind', 'Family_Size', 'Is_Alone', 'Pclass_Sex'], 'Datetime': [], 'Others': []}\\n\\n\\n# Task\\nWrite complete code for 'Current Task'. And avoid duplicating code from 'Done Tasks', such as repeated import of packages, reading data, etc.\\nSpecifically, \\nThe current task is about training a model, please ensure high performance:\\n- Keep in mind that your user prioritizes results and is highly focused on model performance. So, when needed, feel free to use models of any complexity to improve effectiveness, such as XGBoost, CatBoost, etc.\\n- If non-numeric columns exist, perform label encode together with all steps.\\n- Use the data from previous task result directly, do not mock or reload data yourself.\\n- Set suitable hyperparameters for the model, make metrics as high as possible.\\n\\n\\n# Code Steps:\\nStrictly follow steps below when you writing code if it's convenient.\\n\\n\\n# Output Example:\\nwhen current task is \\\"train a lightgbm model on training data\\\", and their are two steps in 'Code Steps', the code be like:\\n```python\\n# Step 1: check data type and convert to numeric\\nojb_cols = train.select_dtypes(include='object').columns.tolist()\\n\\nfor col in obj_cols:\\n encoder = LabelEncoder()\\n train[col] = encoder.fit_transform(train[col].unique().tolist() + ['unknown'])\\n test[col] = test[col].apply(lambda x: x if x in encoder.classes_ else 'unknown')\\n test[col] = encoder.transform(test[col])\\n\\n# Step 2: train lightgbm model\\nmodel = LGBMClassifier()\\nmodel.fit(train, y_train)\\n```end\\n\\n# Constraints:\\n- Ensure the output new code is executable in the same Jupyter notebook with previous tasks code have been executed.\\n- The output code should contain all steps implemented in 'Code Steps'.\\n]\\n\\n[previous impl]\\nfrom sklearn.model_selection import train_test_split\\nfrom sklearn.metrics import accuracy_score\\nfrom xgboost import XGBClassifier\\n\\n# Step 1: Prepare the data\\n# Separate features and target variable\\nX = train_processed.drop('Survived', axis=1)\\ny = train_processed['Survived']\\n\\n# Step 2: Split the data into training and validation sets\\nX_train, X_val, y_train, y_val = train_test_split(X, y, test_size=0.2, random_state=42)\\n\\n# Step 3: Initialize and train the XGBoost model\\n# Note: Hyperparameters are set to values that generally perform well, but further tuning could be done.\\nmodel = XGBClassifier(n_estimators=100, learning_rate=0.05, max_depth=4, subsample=0.8, colsample_bytree=0.8, random_state=42)\\nmodel.fit(X_train, y_train)\\n\\n# Step 4: Evaluate the model\\ny_pred = model.predict(X_val)\\naccuracy = accuracy_score(y_val, y_pred)\\nprint(f'Validation Accuracy: {accuracy:.4f}')\\n[runtime Error]\\n[assistant: from sklearn.model_selection import train_test_split\\nfrom sklearn.metrics import accuracy_score\\nfrom xgboost import XGBClassifier\\n\\n# Step 1: Prepare the data\\n# Separate features and target variable\\nX = train_processed.drop('Survived', axis=1)\\ny = train_processed['Survived']\\n\\n# Step 2: Split the data into training and validation sets\\nX_train, X_val, y_train, y_val = train_test_split(X, y, test_size=0.2, random_state=42)\\n\\n# Step 3: Initialize and train the XGBoost model\\n# Note: Hyperparameters are set to values that generally perform well, but further tuning could be done.\\nmodel = XGBClassifier(n_estimators=100, learning_rate=0.05, max_depth=4, subsample=0.8, colsample_bytree=0.8, random_state=42)\\nmodel.fit(X_train, y_train)\\n\\n# Step 4: Evaluate the model\\ny_pred = model.predict(X_val)\\naccuracy = accuracy_score(y_val, y_pred)\\nprint(f'Validation Accuracy: {accuracy:.4f}'), user: Executed code failed, please reflect the cause of bug and then debug. Truncated to show only last 2000 characters\\n= self._temporary_data\\n 622 else:\\n--> 623 new, cat_codes, feature_names, feature_types = _proxy_transform(\\n 624 data,\\n 625 feature_names,\\n 626 feature_types,\\n 627 self._enable_categorical,\\n 628 )\\n 629 # Stage the data, meta info are copied inside C++ MetaInfo.\\n 630 self._temporary_data = (new, cat_codes, feature_names, feature_types)\\n\\nFile ~/miniconda3/envs/mg_temp/lib/python3.9/site-packages/xgboost/data.py:1315, in _proxy_transform(data, feature_names, feature_types, enable_categorical)\\n 1313 data = pd.DataFrame(data)\\n 1314 if _is_pandas_df(data):\\n-> 1315 arr, feature_names, feature_types = _transform_pandas_df(\\n 1316 data, enable_categorical, feature_names, feature_types\\n 1317 )\\n 1318 arr, _ = _ensure_np_dtype(arr, arr.dtype)\\n 1319 return arr, None, feature_names, feature_types\\n\\nFile ~/miniconda3/envs/mg_temp/lib/python3.9/site-packages/xgboost/data.py:490, in _transform_pandas_df(data, enable_categorical, feature_names, feature_types, meta, meta_type)\\n 483 for dtype in data.dtypes:\\n 484 if not (\\n 485 (dtype.name in _pandas_dtype_mapper)\\n 486 or is_pd_sparse_dtype(dtype)\\n 487 or (is_pd_cat_dtype(dtype) and enable_categorical)\\n 488 or is_pa_ext_dtype(dtype)\\n 489 ):\\n--> 490 _invalid_dataframe_dtype(data)\\n 491 if is_pa_ext_dtype(dtype):\\n 492 pyarrow_extension = True\\n\\nFile ~/miniconda3/envs/mg_temp/lib/python3.9/site-packages/xgboost/data.py:308, in _invalid_dataframe_dtype(data)\\n 306 type_err = \\\"DataFrame.dtypes for data must be int, float, bool or category.\\\"\\n 307 msg = f\\\"\\\"\\\"{type_err} {_ENABLE_CAT_ERR} {err}\\\"\\\"\\\"\\n--> 308 raise ValueError(msg)\\n\\nValueError: DataFrame.dtypes for data must be int, float, bool or category. When categorical type is supplied, The experimental DMatrix parameter`enable_categorical` must be set to `True`. Invalid columns:Title: object, Deck: object\\n\\n]\\n\\nAnalysis the error step by step, provide me improve method and code. Remember to follow [context] rerquirement. Don't forget write code for steps behind the error step.\\n[reflection on previous impl]:\\nxxx\\n\"}]": {
+ "reflection": "The error message indicates that the XGBoost model cannot handle columns with 'object' data types directly. The columns 'Title' and 'Deck' are of 'object' type and need to be encoded as numeric values before they can be used for training the XGBoost model. The error also suggests that if we want to use categorical data, we must set the `enable_categorical` parameter to `True` when creating the DMatrix. However, a more common practice is to convert these object types to numeric types using techniques such as label encoding or one-hot encoding. To resolve this issue, we should encode the 'Title' and 'Deck' columns using label encoding before training the model. Additionally, we should ensure that any other non-numeric columns are also converted to numeric types if they exist.",
+ "improved_impl": "from sklearn.preprocessing import LabelEncoder\n\n# Step 1: Encode non-numeric columns\nencoder = LabelEncoder()\ntrain_processed['Title'] = encoder.fit_transform(train_processed['Title'])\neval_processed['Title'] = encoder.transform(eval_processed['Title'])\ntrain_processed['Deck'] = encoder.fit_transform(train_processed['Deck'].astype(str))\neval_processed['Deck'] = encoder.transform(eval_processed['Deck'].astype(str))\n\n# Step 2: Prepare the data\n# Separate features and target variable\nX = train_processed.drop('Survived', axis=1)\ny = train_processed['Survived']\n\n# Step 3: Train the XGBoost model\nmodel = XGBClassifier(n_estimators=100, learning_rate=0.05, max_depth=4, subsample=0.8, colsample_bytree=0.8, random_state=42)\nmodel.fit(X, y)\n\n# Step 4: Evaluate the model on evaluation data\nX_eval = eval_processed.drop('Survived', axis=1)\ny_eval = eval_processed['Survived']\ny_pred_eval = model.predict(X_eval)\naccuracy_eval = accuracy_score(y_eval, y_pred_eval)\nprint(f'Evaluation Accuracy: {accuracy_eval:.4f}')"
+ },
+ "[{\"role\": \"user\", \"content\": \"\\n# Background\\nAs a data scientist, you need to help user to achieve their goal [This is a titanic passenger survival dataset, your goal is to predict passenger survival outcome. The target column is Survived. Perform data analysis, data preprocessing, feature engineering, and modeling to predict the target. Report accuracy on the eval data. Train data path: 'tests/data/ml_datasets/titanic/split_train.csv', eval data path: 'tests/data/ml_datasets/titanic/split_eval.csv'.] step-by-step in an continuous Jupyter notebook.\\n\\n## Done Tasks\\n```python\\nimport pandas as pd\\ntrain_data = pd.read_csv('tests/data/ml_datasets/titanic/split_train.csv')\\ndisplay(train_data.head())\\ntrain_data.info()\\ntrain_data.describe()\\ntrain_data.describe(include=['O'])\\nmissing_values = train_data.isnull().sum()\\nmissing_values[missing_values > 0]\\nimport seaborn as sns\\nimport matplotlib.pyplot as plt\\nsns.countplot(x='Survived', data=train_data)\\nplt.title('Distribution of Survival')\\nplt.show()\\nsns.barplot(x='Sex', y='Survived', data=train_data)\\nplt.title('Survival Rate by Sex')\\nplt.show()\\nsns.barplot(x='Pclass', y='Survived', data=train_data)\\nplt.title('Survival Rate by Class')\\nplt.show()\\nsns.barplot(x='Embarked', y='Survived', data=train_data)\\nplt.title('Survival Rate by Embarkation Port')\\nplt.show()\\n\\nfrom metagpt.tools.libs.data_preprocess import FillMissingValue\\ntrain_processed = train_data.copy()\\neval_data = pd.read_csv('tests/data/ml_datasets/titanic/split_eval.csv')\\nfill_missing_age = FillMissingValue(features=['Age'], strategy='median')\\ntrain_processed = fill_missing_age.fit_transform(train_processed)\\neval_processed = fill_missing_age.transform(eval_data)\\nfill_missing_embarked = FillMissingValue(features=['Embarked'], strategy='most_frequent')\\ntrain_processed = fill_missing_embarked.fit_transform(train_processed)\\neval_processed = fill_missing_embarked.transform(eval_data)\\ntrain_processed['Cabin_Ind'] = train_processed['Cabin'].isnull().astype(int)\\neval_processed['Cabin_Ind'] = eval_processed['Cabin'].isnull().astype(int)\\nfrom sklearn.preprocessing import LabelEncoder\\nlabel_encoder = LabelEncoder()\\ntrain_processed['Sex'] = label_encoder.fit_transform(train_processed['Sex'])\\neval_processed['Sex'] = label_encoder.transform(eval_processed['Sex'])\\ntrain_processed['Embarked'] = label_encoder.fit_transform(train_processed['Embarked'])\\neval_processed['Embarked'] = label_encoder.transform(eval_processed['Embarked'])\\nfrom sklearn.preprocessing import StandardScaler\\nscaler = StandardScaler()\\nfor feature in ['Age', 'Fare']:\\n train_processed[[feature]] = scaler.fit_transform(train_processed[[feature]])\\n eval_processed[[feature]] = scaler.transform(eval_processed[[feature]])\\ndisplay(train_processed.head())\\n\\ntrain_processed['Family_Size'] = train_processed['SibSp'] + train_processed['Parch']\\neval_processed['Family_Size'] = eval_processed['SibSp'] + eval_processed['Parch']\\ntrain_processed['Is_Alone'] = (train_processed['Family_Size'] == 0).astype(int)\\neval_processed['Is_Alone'] = (eval_processed['Family_Size'] == 0).astype(int)\\ntrain_processed['Title'] = train_processed['Name'].str.extract(' ([A-Za-z]+)\\\\.', expand=False)\\neval_processed['Title'] = eval_processed['Name'].str.extract(' ([A-Za-z]+)\\\\.', expand=False)\\ntrain_processed['Deck'] = train_processed['Cabin'].str[0]\\neval_processed['Deck'] = eval_processed['Cabin'].str[0]\\nfrom metagpt.tools.libs.feature_engineering import CatCross\\ncat_cross = CatCross(cols=['Pclass', 'Sex'])\\ntrain_processed = cat_cross.fit_transform(train_processed)\\neval_processed = cat_cross.transform(eval_processed)\\nfor df in [train_processed, eval_processed]:\\n df.drop(['PassengerId', 'Name', 'Ticket', 'Cabin'], axis=1, inplace=True)\\n\\nfrom sklearn.preprocessing import LabelEncoder\\nencoder = LabelEncoder()\\ntrain_processed['Title'] = encoder.fit_transform(train_processed['Title'])\\neval_processed['Title'] = encoder.transform(eval_processed['Title'])\\ntrain_processed['Deck'] = encoder.fit_transform(train_processed['Deck'].astype(str))\\neval_processed['Deck'] = encoder.transform(eval_processed['Deck'].astype(str))\\nX = train_processed.drop('Survived', axis=1)\\ny = train_processed['Survived']\\nmodel = XGBClassifier(n_estimators=100, learning_rate=0.05, max_depth=4, subsample=0.8, colsample_bytree=0.8, random_state=42)\\nmodel.fit(X, y)\\nX_eval = eval_processed.drop('Survived', axis=1)\\ny_eval = eval_processed['Survived']\\ny_pred_eval = model.predict(X_eval)\\naccuracy_eval = accuracy_score(y_eval, y_pred_eval)\\nprint(f'Evaluation Accuracy: {accuracy_eval:.4f}')\\n```end\\n\\n## Current Task\\nEvaluate the model's accuracy using the eval dataset and report the results.\\n\\n# Latest Data Info\\nLatest data info after previous tasks:\\n\\n\\n# Task\\nWrite complete code for 'Current Task'. And avoid duplicating code from 'Done Tasks', such as repeated import of packages, reading data, etc.\\nSpecifically, \\nThe current task is about evaluating a model, please note the following:\\n- Ensure that the evaluated data is same processed as the training data. If not, remember use object in 'Done Tasks' to transform the data.\\n- Use trained model from previous task result directly, do not mock or reload model yourself.\\n\\n\\n# Code Steps:\\nStrictly follow steps below when you writing code if it's convenient.\\n\\n\\n# Output Example:\\nwhen current task is \\\"train a lightgbm model on training data\\\", and their are two steps in 'Code Steps', the code be like:\\n```python\\n# Step 1: check data type and convert to numeric\\nojb_cols = train.select_dtypes(include='object').columns.tolist()\\n\\nfor col in obj_cols:\\n encoder = LabelEncoder()\\n train[col] = encoder.fit_transform(train[col].unique().tolist() + ['unknown'])\\n test[col] = test[col].apply(lambda x: x if x in encoder.classes_ else 'unknown')\\n test[col] = encoder.transform(test[col])\\n\\n# Step 2: train lightgbm model\\nmodel = LGBMClassifier()\\nmodel.fit(train, y_train)\\n```end\\n\\n# Constraints:\\n- Ensure the output new code is executable in the same Jupyter notebook with previous tasks code have been executed.\\n- The output code should contain all steps implemented in 'Code Steps'.\\n\"}]": {
+ "code": "# Evaluate the model's accuracy using the eval dataset\nfrom sklearn.metrics import accuracy_score\n\n# Predict the target for the evaluation set\ny_pred_eval = model.predict(X_eval)\n\n# Calculate the accuracy for the evaluation set\naccuracy_eval = accuracy_score(y_eval, y_pred_eval)\n\n# Report the accuracy\nprint(f'Evaluation Accuracy: {accuracy_eval:.4f}')"
+ },
+ "\n # Context:\n user: \n## User Requirement\nRun data analysis on sklearn Iris dataset, include a plot\n## Context\n\n## Current Plan\n[]\n## Current Task\n{}\n\n # Task:\n Based on the context, write a plan or modify an existing plan of what you should do to achieve the goal. A plan consists of one to 3 tasks.\n If you are modifying an existing plan, carefully follow the instruction, don't make unnecessary changes. Give the whole plan unless instructed to modify only one task of the plan.\n If you encounter errors on the current task, revise and output the current single task only.\n Output a list of jsons following the format:\n ```json\n [\n {\n \"task_id\": str = \"unique identifier for a task in plan, can be an ordinal\",\n \"dependent_task_ids\": list[str] = \"ids of tasks prerequisite to this task\",\n \"instruction\": \"what you should do in this task, one short phrase or sentence\",\n },\n ...\n ]\n ```\n ": "```json\n[\n {\n \"task_id\": \"1\",\n \"dependent_task_ids\": [],\n \"instruction\": \"Load the sklearn Iris dataset.\"\n },\n {\n \"task_id\": \"2\",\n \"dependent_task_ids\": [\"1\"],\n \"instruction\": \"Perform exploratory data analysis on the Iris dataset.\"\n },\n {\n \"task_id\": \"3\",\n \"dependent_task_ids\": [\"2\"],\n \"instruction\": \"Create a plot visualizing the Iris dataset features.\"\n }\n]\n```",
+ "[{\"role\": \"user\", \"content\": \"\\nPlease assign a task type to each task in the list below from the given categories:\\nTask 1: Load the sklearn Iris dataset.\\nTask 2: Perform exploratory data analysis on the Iris dataset.\\nTask 3: Create a plot visualizing the Iris dataset features.\\n\\n## All Task Type:\\n- **eda**: For performing exploratory data analysis\\n- **data_preprocess**: Only for changing value inplace.\\n- **feature_engineering**: Only for creating new columns for input data.\\n- **model_train**: Only for training model.\\n- **model_evaluate**: Only for evaluating model.\\n- **stable_diffusion**: Related to text2image, image2image using stable diffusion model.\\n- **image2webpage**: For converting image into webpage code.\\n- **web_scraping**: For scraping data from web pages.\\n- **other**: Any tools not in the defined categories\\n\"}]": {
+ "task_type": [
+ "other",
+ "eda",
+ "other"
+ ]
+ },
+ "[{\"role\": \"system\", \"content\": \"You are Code Interpreter, a world-class programmer that can complete any goal by executing code. Strictly follow the plan and generate code step by step. Each step of the code will be executed on the user's machine, and the user will provide the code execution results to you.**Notice: The code for the next step depends on the code for the previous step. Must reuse variables in the lastest other code directly, dont creat it again, it is very import for you. Use !pip install in a standalone block to install missing packages.Usually the libraries you need are already installed.Dont check if packages already imported.**\"}, {\"role\": \"user\", \"content\": \"\\n## User Requirement\\nRun data analysis on sklearn Iris dataset, include a plot\\n## Context\\n\\n## Current Plan\\n[\\n {\\n \\\"task_id\\\": \\\"1\\\",\\n \\\"dependent_task_ids\\\": [],\\n \\\"instruction\\\": \\\"Load the sklearn Iris dataset.\\\",\\n \\\"task_type\\\": \\\"other\\\",\\n \\\"code\\\": \\\"\\\",\\n \\\"result\\\": \\\"\\\",\\n \\\"is_success\\\": false,\\n \\\"is_finished\\\": false\\n },\\n {\\n \\\"task_id\\\": \\\"2\\\",\\n \\\"dependent_task_ids\\\": [\\n \\\"1\\\"\\n ],\\n \\\"instruction\\\": \\\"Perform exploratory data analysis on the Iris dataset.\\\",\\n \\\"task_type\\\": \\\"eda\\\",\\n \\\"code\\\": \\\"\\\",\\n \\\"result\\\": \\\"\\\",\\n \\\"is_success\\\": false,\\n \\\"is_finished\\\": false\\n },\\n {\\n \\\"task_id\\\": \\\"3\\\",\\n \\\"dependent_task_ids\\\": [\\n \\\"2\\\"\\n ],\\n \\\"instruction\\\": \\\"Create a plot visualizing the Iris dataset features.\\\",\\n \\\"task_type\\\": \\\"other\\\",\\n \\\"code\\\": \\\"\\\",\\n \\\"result\\\": \\\"\\\",\\n \\\"is_success\\\": false,\\n \\\"is_finished\\\": false\\n }\\n]\\n## Current Task\\n{\\\"task_id\\\":\\\"1\\\",\\\"dependent_task_ids\\\":[],\\\"instruction\\\":\\\"Load the sklearn Iris dataset.\\\",\\\"task_type\\\":\\\"other\\\",\\\"code_steps\\\":\\\"\\\",\\\"code\\\":\\\"\\\",\\\"result\\\":\\\"\\\",\\\"is_success\\\":false,\\\"is_finished\\\":false}\\n\"}, {\"role\": \"user\", \"content\": \"\\n# Instruction\\nWrite complete code for 'Current Task'. And avoid duplicating code from finished tasks, such as repeated import of packages, reading data, etc.\\nSpecifically, \\n\\n# Capabilities\\n- You can utilize pre-defined tools in any code lines from 'Available Tools' in the form of Python Class.\\n- You can freely combine the use of any other public packages, like sklearn, numpy, pandas, etc..\\n\\n# Available Tools (can be empty):\\nEach Class tool is described in JSON format. When you call a tool, import the tool first.\\n{}\\n\\n# Constraints:\\n- Ensure the output new code is executable in the same Jupyter notebook with previous tasks code have been executed.\\n- Always prioritize using pre-defined tools for the same functionality.\\n\"}]": {
+ "code": "from sklearn.datasets import load_iris\niris_data = load_iris()"
+ },
+ "[{\"role\": \"system\", \"content\": \"You are Code Interpreter, a world-class programmer that can complete any goal by executing code. Strictly follow the plan and generate code step by step. Each step of the code will be executed on the user's machine, and the user will provide the code execution results to you.**Notice: The code for the next step depends on the code for the previous step. Must reuse variables in the lastest other code directly, dont creat it again, it is very import for you. Use !pip install in a standalone block to install missing packages.Usually the libraries you need are already installed.Dont check if packages already imported.**\"}, {\"role\": \"user\", \"content\": \"\\n## User Requirement\\nRun data analysis on sklearn Iris dataset, include a plot\\n## Context\\n\\n## Current Plan\\n[\\n {\\n \\\"task_id\\\": \\\"1\\\",\\n \\\"dependent_task_ids\\\": [],\\n \\\"instruction\\\": \\\"Load the sklearn Iris dataset.\\\",\\n \\\"task_type\\\": \\\"other\\\",\\n \\\"code\\\": \\\"from sklearn.datasets import load_iris\\\\niris_data = load_iris()\\\",\\n \\\"result\\\": \\\"\\\",\\n \\\"is_success\\\": true,\\n \\\"is_finished\\\": true\\n },\\n {\\n \\\"task_id\\\": \\\"2\\\",\\n \\\"dependent_task_ids\\\": [\\n \\\"1\\\"\\n ],\\n \\\"instruction\\\": \\\"Perform exploratory data analysis on the Iris dataset.\\\",\\n \\\"task_type\\\": \\\"eda\\\",\\n \\\"code\\\": \\\"\\\",\\n \\\"result\\\": \\\"\\\",\\n \\\"is_success\\\": false,\\n \\\"is_finished\\\": false\\n },\\n {\\n \\\"task_id\\\": \\\"3\\\",\\n \\\"dependent_task_ids\\\": [\\n \\\"2\\\"\\n ],\\n \\\"instruction\\\": \\\"Create a plot visualizing the Iris dataset features.\\\",\\n \\\"task_type\\\": \\\"other\\\",\\n \\\"code\\\": \\\"\\\",\\n \\\"result\\\": \\\"\\\",\\n \\\"is_success\\\": false,\\n \\\"is_finished\\\": false\\n }\\n]\\n## Current Task\\n{\\\"task_id\\\":\\\"2\\\",\\\"dependent_task_ids\\\":[\\\"1\\\"],\\\"instruction\\\":\\\"Perform exploratory data analysis on the Iris dataset.\\\",\\\"task_type\\\":\\\"eda\\\",\\\"code_steps\\\":\\\"\\\",\\\"code\\\":\\\"\\\",\\\"result\\\":\\\"\\\",\\\"is_success\\\":false,\\\"is_finished\\\":false}\\n\"}, {\"role\": \"user\", \"content\": \"\\n# Instruction\\nWrite complete code for 'Current Task'. And avoid duplicating code from finished tasks, such as repeated import of packages, reading data, etc.\\nSpecifically, \\n\\n# Capabilities\\n- You can utilize pre-defined tools in any code lines from 'Available Tools' in the form of Python Class.\\n- You can freely combine the use of any other public packages, like sklearn, numpy, pandas, etc..\\n\\n# Available Tools (can be empty):\\nEach Class tool is described in JSON format. When you call a tool, import the tool first.\\n{}\\n\\n# Constraints:\\n- Ensure the output new code is executable in the same Jupyter notebook with previous tasks code have been executed.\\n- Always prioritize using pre-defined tools for the same functionality.\\n\"}]": {
+ "code": "import pandas as pd\n\n# Convert the loaded Iris dataset into a DataFrame for easier manipulation\niris_df = pd.DataFrame(data=iris_data.data, columns=iris_data.feature_names)\niris_df['target'] = iris_data.target\n\n# Display basic statistical details like percentile, mean, std etc. of a data frame\niris_stats = iris_df.describe()\n\n# Display the first few rows of the DataFrame\niris_head = iris_df.head()\n\n# Display the class distribution\niris_target_counts = iris_df['target'].value_counts()\n\n# Output the results\nprint('Basic Statistical Details:\\n', iris_stats)\nprint('\\nFirst Five Rows:\\n', iris_head)\nprint('\\nClass Distribution:\\n', iris_target_counts)"
+ },
+ "[{\"role\": \"system\", \"content\": \"You are Code Interpreter, a world-class programmer that can complete any goal by executing code. Strictly follow the plan and generate code step by step. Each step of the code will be executed on the user's machine, and the user will provide the code execution results to you.**Notice: The code for the next step depends on the code for the previous step. Must reuse variables in the lastest other code directly, dont creat it again, it is very import for you. Use !pip install in a standalone block to install missing packages.Usually the libraries you need are already installed.Dont check if packages already imported.**\"}, {\"role\": \"user\", \"content\": \"\\n## User Requirement\\nRun data analysis on sklearn Iris dataset, include a plot\\n## Context\\n\\n## Current Plan\\n[\\n {\\n \\\"task_id\\\": \\\"1\\\",\\n \\\"dependent_task_ids\\\": [],\\n \\\"instruction\\\": \\\"Load the sklearn Iris dataset.\\\",\\n \\\"task_type\\\": \\\"other\\\",\\n \\\"code\\\": \\\"from sklearn.datasets import load_iris\\\\niris_data = load_iris()\\\",\\n \\\"result\\\": \\\"\\\",\\n \\\"is_success\\\": true,\\n \\\"is_finished\\\": true\\n },\\n {\\n \\\"task_id\\\": \\\"2\\\",\\n \\\"dependent_task_ids\\\": [\\n \\\"1\\\"\\n ],\\n \\\"instruction\\\": \\\"Perform exploratory data analysis on the Iris dataset.\\\",\\n \\\"task_type\\\": \\\"eda\\\",\\n \\\"code\\\": \\\"import pandas as pd\\\\n\\\\n# Convert the loaded Iris dataset into a DataFrame for easier manipulation\\\\niris_df = pd.DataFrame(data=iris_data.data, columns=iris_data.feature_names)\\\\niris_df['target'] = iris_data.target\\\\n\\\\n# Display basic statistical details like percentile, mean, std etc. of a data frame\\\\niris_stats = iris_df.describe()\\\\n\\\\n# Display the first few rows of the DataFrame\\\\niris_head = iris_df.head()\\\\n\\\\n# Display the class distribution\\\\niris_target_counts = iris_df['target'].value_counts()\\\\n\\\\n# Output the results\\\\nprint('Basic Statistical Details:\\\\\\\\n', iris_stats)\\\\nprint('\\\\\\\\nFirst Five Rows:\\\\\\\\n', iris_head)\\\\nprint('\\\\\\\\nClass Distribution:\\\\\\\\n', iris_target_counts)\\\",\\n \\\"result\\\": \\\"Basic Statistical Details:\\\\n sepal length (cm) sepal width (cm) petal length (cm) \\\\\\\\\\\\ncount 150.000000 150.000000 150.000000 \\\\nmean 5.843333 3.057333 3.758000 \\\\nstd 0.828066 0.435866 1.765298 \\\\nmin 4.300000 2.000000 1.000000 \\\\n25% 5.100000 2.800000 1.600000 \\\\n50% 5.800000 3.000000 4.350000 \\\\n75% 6.400000 3.300000 5.100000 \\\\nmax 7.900000 4.400000 6.900000 \\\\n\\\\n petal width (cm) target \\\\ncount 150.000000 150.000000 \\\\nmean 1.199333 1.000000 \\\\nstd 0.762238 0.819232 \\\\nmin 0.100000 0.000000 \\\\n25% 0.300000 0.000000 \\\\n50% 1.300000 1.000000 \\\\n75% 1.800000 2.000000 \\\\nmax 2.500000 2.000000 \\\\n\\\\nFirst Five Rows:\\\\n sepal length (cm) sepal width (cm) petal length (cm) petal width (cm) \\\\\\\\\\\\n0 5.1 3.5 1.4 0.2 \\\\n1 4.9 3.0 1.4 0.2 \\\\n2 4.7 3.2 1.3 0.2 \\\\n3 4.6 3.1 1.5 0.2 \\\\n4 5.0 3.6 1.4 0.2 \\\\n\\\\n target \\\\n0 0 \\\\n1 0 \\\\n2 0 \\\\n3 0 \\\\n4 0 \\\\n\\\\nClass Distribution:\\\\n target\\\\n0 50\\\\n1 50\\\\n2 50\\\\nName: count, dtype: int64\\\\n\\\",\\n \\\"is_success\\\": true,\\n \\\"is_finished\\\": true\\n },\\n {\\n \\\"task_id\\\": \\\"3\\\",\\n \\\"dependent_task_ids\\\": [\\n \\\"2\\\"\\n ],\\n \\\"instruction\\\": \\\"Create a plot visualizing the Iris dataset features.\\\",\\n \\\"task_type\\\": \\\"other\\\",\\n \\\"code\\\": \\\"\\\",\\n \\\"result\\\": \\\"\\\",\\n \\\"is_success\\\": false,\\n \\\"is_finished\\\": false\\n }\\n]\\n## Current Task\\n{\\\"task_id\\\":\\\"3\\\",\\\"dependent_task_ids\\\":[\\\"2\\\"],\\\"instruction\\\":\\\"Create a plot visualizing the Iris dataset features.\\\",\\\"task_type\\\":\\\"other\\\",\\\"code_steps\\\":\\\"\\\",\\\"code\\\":\\\"\\\",\\\"result\\\":\\\"\\\",\\\"is_success\\\":false,\\\"is_finished\\\":false}\\n\"}, {\"role\": \"user\", \"content\": \"\\n# Instruction\\nWrite complete code for 'Current Task'. And avoid duplicating code from finished tasks, such as repeated import of packages, reading data, etc.\\nSpecifically, \\n\\n# Capabilities\\n- You can utilize pre-defined tools in any code lines from 'Available Tools' in the form of Python Class.\\n- You can freely combine the use of any other public packages, like sklearn, numpy, pandas, etc..\\n\\n# Available Tools (can be empty):\\nEach Class tool is described in JSON format. When you call a tool, import the tool first.\\n{}\\n\\n# Constraints:\\n- Ensure the output new code is executable in the same Jupyter notebook with previous tasks code have been executed.\\n- Always prioritize using pre-defined tools for the same functionality.\\n\"}]": {
+ "code": "import matplotlib.pyplot as plt\nimport seaborn as sns\n\n# Set the style of seaborn\nsns.set(style=\"whitegrid\")\n\n# Create a pairplot to visualize the relationships between features\npair_plot = sns.pairplot(iris_df, hue='target', markers=['o', 's', 'D'], palette='husl')\n\n# Show the plot\nplt.show()"
+ },
+ "\n # Context:\n user: run analysis on sklearn iris dataset\n # Task:\n Based on the context, write a plan or modify an existing plan of what you should do to achieve the goal. A plan consists of one to 5 tasks.\n If you are modifying an existing plan, carefully follow the instruction, don't make unnecessary changes. Give the whole plan unless instructed to modify only one task of the plan.\n If you encounter errors on the current task, revise and output the current single task only.\n Output a list of jsons following the format:\n ```json\n [\n {\n \"task_id\": str = \"unique identifier for a task in plan, can be an ordinal\",\n \"dependent_task_ids\": list[str] = \"ids of tasks prerequisite to this task\",\n \"instruction\": \"what you should do in this task, one short phrase or sentence\",\n },\n ...\n ]\n ```\n ": "```json\n[\n {\n \"task_id\": \"1\",\n \"dependent_task_ids\": [],\n \"instruction\": \"Import the Iris dataset from scikit-learn.\"\n },\n {\n \"task_id\": \"2\",\n \"dependent_task_ids\": [\"1\"],\n \"instruction\": \"Perform exploratory data analysis to understand the dataset.\"\n },\n {\n \"task_id\": \"3\",\n \"dependent_task_ids\": [\"2\"],\n \"instruction\": \"Preprocess the data if necessary (e.g., scaling, encoding).\"\n },\n {\n \"task_id\": \"4\",\n \"dependent_task_ids\": [\"3\"],\n \"instruction\": \"Split the dataset into training and testing sets.\"\n },\n {\n \"task_id\": \"5\",\n \"dependent_task_ids\": [\"4\"],\n \"instruction\": \"Choose a suitable model and train it on the dataset.\"\n },\n {\n \"task_id\": \"6\",\n \"dependent_task_ids\": [\"5\"],\n \"instruction\": \"Evaluate the model's performance on the test set.\"\n },\n {\n \"task_id\": \"7\",\n \"dependent_task_ids\": [\"6\"],\n \"instruction\": \"Report the results of the analysis.\"\n }\n]\n```",
+ "[{\"role\": \"user\", \"content\": \"\\n## User Requirement:\\n对数据集进行数据清洗\\n\\n## Task\\nRecommend up to five tools from 'Available Tools' that can help solve the 'User Requirement'. \\nThis is a detailed code steps for current task. You can refer to it when recommending tools.\\n\\n\\n## Available Tools:\\n{'FillMissingValue': 'Completing missing values with simple strategies', 'MinMaxScale': 'Transform features by scaling each feature to a range, witch is (0, 1)', 'StandardScale': 'Standardize features by removing the mean and scaling to unit variance', 'MaxAbsScale': 'cale each feature by its maximum absolute value', 'RobustScale': 'Apply the RobustScaler to scale features using statistics that are robust to outliers.', 'OrdinalEncode': 'Encode categorical features as ordinal integers.', 'OneHotEncode': 'Apply one-hot encoding to specified categorical columns, the original columns will be dropped.', 'LabelEncode': 'Apply label encoding to specified categorical columns in-place.'}\\n\\n## Tool Selection and Instructions:\\n- Select tools most relevant to completing the 'User Requirement'.\\n- If you believe that no tools are suitable, indicate with an empty list.\\n- Only list the names of the tools, not the full schema of each tool.\\n- Ensure selected tools are listed in 'Available Tools'.\\n\"}]": {
+ "recommend_tools": [
+ "FillMissingValue",
+ "MinMaxScale",
+ "StandardScale",
+ "RobustScale",
+ "OneHotEncode"
+ ]
+ },
+ "[{\"role\": \"user\", \"content\": \"\\n# Background\\nAs a data scientist, you need to help user to achieve their goal [构造数据集并进行数据清洗] step-by-step in an continuous Jupyter notebook.\\n\\n## Done Tasks\\n```python\\n import pandas as pd\\n df = pd.DataFrame({\\n 'a': [1, 2, 3, 4, 5],\\n 'b': [1.1, 2.2, 3.3, 4.4, np.nan],\\n 'c': ['aa', 'bb', 'cc', 'dd', 'ee'],\\n 'd': [1, 2, 3, 4, 5]\\n })\\n```end\\n\\n## Current Task\\n对数据集进行数据清洗\\n\\n# Latest Data Info\\nLatest data info after previous tasks:\\n\\n\\n# Task\\nWrite complete code for 'Current Task'. And avoid duplicating code from 'Done Tasks', such as repeated import of packages, reading data, etc.\\nSpecifically, \\nThe current task is about data preprocessing, please note the following:\\n- Monitor data types per column, applying appropriate methods.\\n- Ensure operations are on existing dataset columns.\\n- Avoid writing processed data to files.\\n- Avoid any change to label column, such as standardization, etc.\\n- Prefer alternatives to one-hot encoding for categorical data.\\n- Only encode or scale necessary columns to allow for potential feature-specific engineering tasks (like time_extract, binning, extraction, etc.) later.\\n- Each step do data preprocessing to train, must do same for test separately at the same time.\\n\\n\\n# Code Steps:\\nStrictly follow steps below when you writing code if it's convenient.\\n\\n\\n# Capabilities\\n- You can utilize pre-defined tools in any code lines from 'Available Tools' in the form of Python Class.\\n- You can freely combine the use of any other public packages, like sklearn, numpy, pandas, etc..\\n\\n# Available Tools:\\nEach Class tool is described in JSON format. When you call a tool, import the tool from its path first.\\n{'FillMissingValue': {'type': 'class', 'description': 'Completing missing values with simple strategies', 'methods': {'__init__': {'description': 'Initialize self.', 'parameters': {'properties': {'features': {'type': 'list', 'description': 'columns to be processed'}, 'strategy': {'type': 'str', 'description': 'the imputation strategy, notice mean/median can only be used for numeric features', 'default': 'mean', 'enum': ['mean', 'median', 'most_frequent', 'constant']}, 'fill_value': {'type': 'int', 'description': 'fill_value is used to replace all occurrences of missing_values', 'default': None}}, 'required': ['features']}}, 'fit': {'description': 'Fit the FillMissingValue model.', 'parameters': {'properties': {'df': {'type': 'DataFrame', 'description': 'The input DataFrame.'}}, 'required': ['df']}}, 'transform': {'description': 'Transform the input DataFrame with the fitted model.', 'parameters': {'properties': {'df': {'type': 'DataFrame', 'description': 'The input DataFrame.'}}, 'required': ['df']}, 'returns': {'df': {'type': 'DataFrame', 'description': 'The transformed DataFrame.'}}}, 'fit_transform': {'description': 'Fit and transform the input DataFrame.', 'parameters': {'properties': {'df': {'type': 'DataFrame', 'description': 'The input DataFrame.'}}, 'required': ['df']}, 'returns': {'df': {'type': 'DataFrame', 'description': 'The transformed DataFrame.'}}}}, 'tool_path': 'metagpt/tools/libs/data_preprocess.py'}, 'MinMaxScale': {'type': 'class', 'description': 'Transform features by scaling each feature to a range, witch is (0, 1)', 'methods': {'__init__': {'description': 'Initialize self.', 'parameters': {'properties': {'features': {'type': 'list', 'description': 'columns to be processed'}}, 'required': ['features']}}, 'fit': {'description': 'Fit the MinMaxScale model.', 'parameters': {'properties': {'df': {'type': 'DataFrame', 'description': 'The input DataFrame.'}}, 'required': ['df']}}, 'transform': {'description': 'Transform the input DataFrame with the fitted model.', 'parameters': {'properties': {'df': {'type': 'DataFrame', 'description': 'The input DataFrame.'}}, 'required': ['df']}, 'returns': {'df': {'type': 'DataFrame', 'description': 'The transformed DataFrame.'}}}, 'fit_transform': {'description': 'Fit and transform the input DataFrame.', 'parameters': {'properties': {'df': {'type': 'DataFrame', 'description': 'The input DataFrame.'}}, 'required': ['df']}, 'returns': {'df': {'type': 'DataFrame', 'description': 'The transformed DataFrame.'}}}}, 'tool_path': 'metagpt/tools/libs/data_preprocess.py'}, 'StandardScale': {'type': 'class', 'description': 'Standardize features by removing the mean and scaling to unit variance', 'methods': {'__init__': {'description': 'Initialize self.', 'parameters': {'properties': {'features': {'type': 'list', 'description': 'columns to be processed'}}, 'required': ['features']}}, 'fit': {'description': 'Fit the StandardScale model.', 'parameters': {'properties': {'df': {'type': 'DataFrame', 'description': 'The input DataFrame.'}}, 'required': ['df']}}, 'transform': {'description': 'Transform the input DataFrame with the fitted model.', 'parameters': {'properties': {'df': {'type': 'DataFrame', 'description': 'The input DataFrame.'}}, 'required': ['df']}, 'returns': {'df': {'type': 'DataFrame', 'description': 'The transformed DataFrame.'}}}, 'fit_transform': {'description': 'Fit and transform the input DataFrame.', 'parameters': {'properties': {'df': {'type': 'DataFrame', 'description': 'The input DataFrame.'}}, 'required': ['df']}, 'returns': {'df': {'type': 'DataFrame', 'description': 'The transformed DataFrame.'}}}}, 'tool_path': 'metagpt/tools/libs/data_preprocess.py'}, 'RobustScale': {'type': 'class', 'description': 'Apply the RobustScaler to scale features using statistics that are robust to outliers.', 'methods': {'__init__': {'description': 'Initialize the RobustScale instance with feature names. ', 'parameters': {'properties': {'features': {'type': 'list', 'description': 'List of feature names to be scaled.'}}, 'required': ['features']}}, 'fit': {'description': 'Compute the median and IQR for scaling. ', 'parameters': {'properties': {'df': {'type': 'pd.DataFrame', 'description': 'Dataframe containing the features.'}}, 'required': ['df']}}, 'fit_transform': {'description': 'Fit and transform the input DataFrame. ', 'parameters': {'properties': {'df': {'type': 'pd.DataFrame', 'description': 'The input DataFrame.'}}, 'required': ['df']}, 'returns': [{'type': 'pd.DataFrame', 'description': 'The transformed DataFrame.'}]}, 'transform': {'description': 'Scale features using the previously computed median and IQR. ', 'parameters': {'properties': {'df': {'type': 'pd.DataFrame', 'description': 'Dataframe containing the features to be scaled.'}}, 'required': ['df']}, 'returns': [{'type': 'pd.DataFrame', 'description': 'A new dataframe with scaled features.'}]}}, 'tool_path': 'metagpt/tools/libs/data_preprocess.py'}, 'OneHotEncode': {'type': 'class', 'description': 'Apply one-hot encoding to specified categorical columns, the original columns will be dropped.', 'methods': {'__init__': {'description': 'Initialize self.', 'parameters': {'properties': {'features': {'type': 'list', 'description': 'Categorical columns to be one-hot encoded and dropped'}}, 'required': ['features']}}, 'fit': {'description': 'Fit the OneHotEncoding model.', 'parameters': {'properties': {'df': {'type': 'DataFrame', 'description': 'The input DataFrame.'}}, 'required': ['df']}}, 'transform': {'description': 'Transform the input DataFrame with the fitted model.', 'parameters': {'properties': {'df': {'type': 'DataFrame', 'description': 'The input DataFrame.'}}, 'required': ['df']}, 'returns': {'df': {'type': 'DataFrame', 'description': 'The transformed DataFrame.'}}}, 'fit_transform': {'description': 'Fit and transform the input DataFrame.', 'parameters': {'properties': {'df': {'type': 'DataFrame', 'description': 'The input DataFrame.'}}, 'required': ['df']}, 'returns': {'df': {'type': 'DataFrame', 'description': 'The transformed DataFrame.'}}}}, 'tool_path': 'metagpt/tools/libs/data_preprocess.py'}}\\n\\n# Output Example:\\nwhen current task is \\\"do data preprocess, like fill missing value, handle outliers, etc.\\\", and their are two steps in 'Code Steps', the code be like:\\n```python\\n# Step 1: fill missing value\\n# Tools used: ['FillMissingValue']\\nfrom metagpt.tools.libs.data_preprocess import FillMissingValue\\n\\ntrain_processed = train.copy()\\ntest_processed = test.copy()\\nnum_cols = train_processed.select_dtypes(include='number').columns.tolist()\\nif 'label' in num_cols:\\n num_cols.remove('label')\\nfill_missing_value = FillMissingValue(features=num_cols, strategy='mean')\\nfill_missing_value.fit(train_processed)\\ntrain_processed = fill_missing_value.transform(train_processed)\\ntest_processed = fill_missing_value.transform(test_processed)\\n\\n# Step 2: handle outliers\\nfor col in num_cols:\\n low, high = train_processed[col].quantile([0.01, 0.99])\\n train_processed[col] = train_processed[col].clip(low, high)\\n test_processed[col] = test_processed[col].clip(low, high)\\n```end\\n\\n# Constraints:\\n- Ensure the output new code is executable in the same Jupyter notebook with previous tasks code have been executed.\\n- Always prioritize using pre-defined tools for the same functionality.\\n- Always copy the DataFrame before processing it and use the copy to process.\\n- The output code should contain all steps implemented correctly in 'Code Steps'.\\n\"}]": {
+ "code": "# Step 1: Fill missing values\nfrom metagpt.tools.libs.data_preprocess import FillMissingValue\n\ndf_processed = df.copy()\n\n# Identify numeric columns with missing values\nnumeric_cols_with_na = df_processed.select_dtypes(include=['number']).columns[df_processed.isna().any()].tolist()\n\n# Fill missing values with the mean for numeric columns\nfill_missing_value = FillMissingValue(features=numeric_cols_with_na, strategy='mean')\ndf_processed = fill_missing_value.fit_transform(df_processed)\n\n# Step 2: Scale numeric columns\nfrom metagpt.tools.libs.data_preprocess import MinMaxScale\n\n# Identify numeric columns\nnumeric_cols = df_processed.select_dtypes(include=['number']).columns.tolist()\n\n# Scale numeric columns\nmin_max_scaler = MinMaxScale(features=numeric_cols)\ndf_processed = min_max_scaler.fit_transform(df_processed)\n\n# Note: Since there is no separate test set provided, the scaling is only applied to the existing dataset."
+ },
+ "[{\"role\": \"user\", \"content\": \"\\n## User Requirement:\\nclean and preprocess the data\\n\\n## Task\\nRecommend up to five tools from 'Available Tools' that can help solve the 'User Requirement'. \\nThis is a detailed code steps for current task. You can refer to it when recommending tools.\\n\\n\\n## Available Tools:\\n{'FillMissingValue': 'Filling missing values', 'SplitBins': 'Bin continuous data into intervals and return the bin identifier encoded as an integer value'}\\n\\n## Tool Selection and Instructions:\\n- Select tools most relevant to completing the 'User Requirement'.\\n- If you believe that no tools are suitable, indicate with an empty list.\\n- Only list the names of the tools, not the full schema of each tool.\\n- Ensure selected tools are listed in 'Available Tools'.\\n\"}]": {
+ "recommend_tools": [
+ "FillMissingValue"
+ ]
+ },
+ "[{\"role\": \"system\", \"content\": \"You are Code Interpreter, a world-class programmer that can complete any goal by executing code. Strictly follow the plan and generate code step by step. Each step of the code will be executed on the user's machine, and the user will provide the code execution results to you.**Notice: The code for the next step depends on the code for the previous step. Must reuse variables in the lastest other code directly, dont creat it again, it is very import for you. Use !pip install in a standalone block to install missing packages.Usually the libraries you need are already installed.Dont check if packages already imported.**\"}, {\"role\": \"user\", \"content\": \"\\n## User Requirement\\n构造数据集并进行数据清洗\\n## Context\\n\\n## Current Plan\\n[Task(task_id='1', dependent_task_ids=[], instruction='随机生成一个pandas DataFrame数据集', task_type='other', code_steps='', code=\\\"\\\\n import pandas as pd\\\\n df = pd.DataFrame({\\\\n 'a': [1, 2, 3, 4, 5],\\\\n 'b': [1.1, 2.2, 3.3, 4.4, np.nan],\\\\n 'c': ['aa', 'bb', 'cc', 'dd', 'ee'],\\\\n 'd': [1, 2, 3, 4, 5]\\\\n })\\\\n \\\", result='', is_success=False, is_finished=True), Task(task_id='2', dependent_task_ids=['1'], instruction='对数据集进行数据清洗', task_type='data_preprocess', code_steps='', code='', result='', is_success=False, is_finished=False)]\\n## Current Task\\n{\\\"task_id\\\":\\\"2\\\",\\\"dependent_task_ids\\\":[\\\"1\\\"],\\\"instruction\\\":\\\"对数据集进行数据清洗\\\",\\\"task_type\\\":\\\"data_preprocess\\\",\\\"code_steps\\\":\\\"\\\",\\\"code\\\":\\\"\\\",\\\"result\\\":\\\"\\\",\\\"is_success\\\":false,\\\"is_finished\\\":false}\\n\"}, {\"role\": \"user\", \"content\": \"\\n# Instruction\\nWrite complete code for 'Current Task'. And avoid duplicating code from finished tasks, such as repeated import of packages, reading data, etc.\\nSpecifically, \\nThe current task is about data preprocessing, please note the following:\\n- Monitor data types per column, applying appropriate methods.\\n- Ensure operations are on existing dataset columns.\\n- Avoid writing processed data to files.\\n- Avoid any change to label column, such as standardization, etc.\\n- Prefer alternatives to one-hot encoding for categorical data.\\n- Only encode or scale necessary columns to allow for potential feature-specific engineering tasks (like time_extract, binning, extraction, etc.) later.\\n- Each step do data preprocessing to train, must do same for test separately at the same time.\\n\\n\\n# Capabilities\\n- You can utilize pre-defined tools in any code lines from 'Available Tools' in the form of Python Class.\\n- You can freely combine the use of any other public packages, like sklearn, numpy, pandas, etc..\\n\\n# Available Tools (can be empty):\\nEach Class tool is described in JSON format. When you call a tool, import the tool first.\\n{'FillMissingValue': {'type': 'class', 'description': 'Completing missing values with simple strategies', 'methods': {'__init__': {'description': 'Initialize self.', 'parameters': {'properties': {'features': {'type': 'list', 'description': 'columns to be processed'}, 'strategy': {'type': 'str', 'description': 'the imputation strategy, notice mean/median can only be used for numeric features', 'default': 'mean', 'enum': ['mean', 'median', 'most_frequent', 'constant']}, 'fill_value': {'type': 'int', 'description': 'fill_value is used to replace all occurrences of missing_values', 'default': None}}, 'required': ['features']}}, 'fit': {'description': 'Fit the FillMissingValue model.', 'parameters': {'properties': {'df': {'type': 'DataFrame', 'description': 'The input DataFrame.'}}, 'required': ['df']}}, 'transform': {'description': 'Transform the input DataFrame with the fitted model.', 'parameters': {'properties': {'df': {'type': 'DataFrame', 'description': 'The input DataFrame.'}}, 'required': ['df']}, 'returns': {'df': {'type': 'DataFrame', 'description': 'The transformed DataFrame.'}}}, 'fit_transform': {'description': 'Fit and transform the input DataFrame.', 'parameters': {'properties': {'df': {'type': 'DataFrame', 'description': 'The input DataFrame.'}}, 'required': ['df']}, 'returns': {'df': {'type': 'DataFrame', 'description': 'The transformed DataFrame.'}}}}, 'tool_path': 'metagpt/tools/libs/data_preprocess.py'}, 'MinMaxScale': {'type': 'class', 'description': 'Transform features by scaling each feature to a range, witch is (0, 1)', 'methods': {'__init__': {'description': 'Initialize self.', 'parameters': {'properties': {'features': {'type': 'list', 'description': 'columns to be processed'}}, 'required': ['features']}}, 'fit': {'description': 'Fit the MinMaxScale model.', 'parameters': {'properties': {'df': {'type': 'DataFrame', 'description': 'The input DataFrame.'}}, 'required': ['df']}}, 'transform': {'description': 'Transform the input DataFrame with the fitted model.', 'parameters': {'properties': {'df': {'type': 'DataFrame', 'description': 'The input DataFrame.'}}, 'required': ['df']}, 'returns': {'df': {'type': 'DataFrame', 'description': 'The transformed DataFrame.'}}}, 'fit_transform': {'description': 'Fit and transform the input DataFrame.', 'parameters': {'properties': {'df': {'type': 'DataFrame', 'description': 'The input DataFrame.'}}, 'required': ['df']}, 'returns': {'df': {'type': 'DataFrame', 'description': 'The transformed DataFrame.'}}}}, 'tool_path': 'metagpt/tools/libs/data_preprocess.py'}, 'StandardScale': {'type': 'class', 'description': 'Standardize features by removing the mean and scaling to unit variance', 'methods': {'__init__': {'description': 'Initialize self.', 'parameters': {'properties': {'features': {'type': 'list', 'description': 'columns to be processed'}}, 'required': ['features']}}, 'fit': {'description': 'Fit the StandardScale model.', 'parameters': {'properties': {'df': {'type': 'DataFrame', 'description': 'The input DataFrame.'}}, 'required': ['df']}}, 'transform': {'description': 'Transform the input DataFrame with the fitted model.', 'parameters': {'properties': {'df': {'type': 'DataFrame', 'description': 'The input DataFrame.'}}, 'required': ['df']}, 'returns': {'df': {'type': 'DataFrame', 'description': 'The transformed DataFrame.'}}}, 'fit_transform': {'description': 'Fit and transform the input DataFrame.', 'parameters': {'properties': {'df': {'type': 'DataFrame', 'description': 'The input DataFrame.'}}, 'required': ['df']}, 'returns': {'df': {'type': 'DataFrame', 'description': 'The transformed DataFrame.'}}}}, 'tool_path': 'metagpt/tools/libs/data_preprocess.py'}, 'RobustScale': {'type': 'class', 'description': 'Apply the RobustScaler to scale features using statistics that are robust to outliers.', 'methods': {'__init__': {'description': 'Initialize the RobustScale instance with feature names. ', 'parameters': {'properties': {'features': {'type': 'list', 'description': 'List of feature names to be scaled.'}}, 'required': ['features']}}, 'fit': {'description': 'Compute the median and IQR for scaling. ', 'parameters': {'properties': {'df': {'type': 'pd.DataFrame', 'description': 'Dataframe containing the features.'}}, 'required': ['df']}}, 'fit_transform': {'description': 'Fit and transform the input DataFrame. ', 'parameters': {'properties': {'df': {'type': 'pd.DataFrame', 'description': 'The input DataFrame.'}}, 'required': ['df']}, 'returns': [{'type': 'pd.DataFrame', 'description': 'The transformed DataFrame.'}]}, 'transform': {'description': 'Scale features using the previously computed median and IQR. ', 'parameters': {'properties': {'df': {'type': 'pd.DataFrame', 'description': 'Dataframe containing the features to be scaled.'}}, 'required': ['df']}, 'returns': [{'type': 'pd.DataFrame', 'description': 'A new dataframe with scaled features.'}]}}, 'tool_path': 'metagpt/tools/libs/data_preprocess.py'}, 'OneHotEncode': {'type': 'class', 'description': 'Apply one-hot encoding to specified categorical columns, the original columns will be dropped.', 'methods': {'__init__': {'description': 'Initialize self.', 'parameters': {'properties': {'features': {'type': 'list', 'description': 'Categorical columns to be one-hot encoded and dropped'}}, 'required': ['features']}}, 'fit': {'description': 'Fit the OneHotEncoding model.', 'parameters': {'properties': {'df': {'type': 'DataFrame', 'description': 'The input DataFrame.'}}, 'required': ['df']}}, 'transform': {'description': 'Transform the input DataFrame with the fitted model.', 'parameters': {'properties': {'df': {'type': 'DataFrame', 'description': 'The input DataFrame.'}}, 'required': ['df']}, 'returns': {'df': {'type': 'DataFrame', 'description': 'The transformed DataFrame.'}}}, 'fit_transform': {'description': 'Fit and transform the input DataFrame.', 'parameters': {'properties': {'df': {'type': 'DataFrame', 'description': 'The input DataFrame.'}}, 'required': ['df']}, 'returns': {'df': {'type': 'DataFrame', 'description': 'The transformed DataFrame.'}}}}, 'tool_path': 'metagpt/tools/libs/data_preprocess.py'}}\\n\\n# Constraints:\\n- Ensure the output new code is executable in the same Jupyter notebook with previous tasks code have been executed.\\n- Always prioritize using pre-defined tools for the same functionality.\\n\"}]": {
+ "code": "from metagpt.tools.libs.data_preprocess import FillMissingValue\n\n# Instantiate the FillMissingValue class for numeric columns with missing values\ncolumns_with_missing_values = ['b']\nfill_missing_value = FillMissingValue(features=columns_with_missing_values, strategy='mean')\n\n# Apply the fit_transform method to the DataFrame to fill missing values\ndf = fill_missing_value.fit_transform(df)\n"
+ },
+ "[{\"role\": \"system\", \"content\": \"You are Code Interpreter, a world-class programmer that can complete any goal by executing code. Strictly follow the plan and generate code step by step. Each step of the code will be executed on the user's machine, and the user will provide the code execution results to you.**Notice: The code for the next step depends on the code for the previous step. Must reuse variables in the lastest other code directly, dont creat it again, it is very import for you. Use !pip install in a standalone block to install missing packages.Usually the libraries you need are already installed.Dont check if packages already imported.**\"}, {\"role\": \"user\", \"content\": \"\\n ## User Requirement\\n read a dataset test.csv and print its head\\n ## Current Plan\\n [\\n {\\n \\\"task_id\\\": \\\"1\\\",\\n \\\"dependent_task_ids\\\": [],\\n \\\"instruction\\\": \\\"import pandas and load the dataset from 'test.csv'.\\\",\\n \\\"task_type\\\": \\\"\\\",\\n \\\"code\\\": \\\"\\\",\\n \\\"result\\\": \\\"\\\",\\n \\\"is_finished\\\": false\\n },\\n {\\n \\\"task_id\\\": \\\"2\\\",\\n \\\"dependent_task_ids\\\": [\\n \\\"1\\\"\\n ],\\n \\\"instruction\\\": \\\"Print the head of the dataset to display the first few rows.\\\",\\n \\\"task_type\\\": \\\"\\\",\\n \\\"code\\\": \\\"\\\",\\n \\\"result\\\": \\\"\\\",\\n \\\"is_finished\\\": false\\n }\\n ]\\n ## Current Task\\n {\\\"task_id\\\": \\\"1\\\", \\\"dependent_task_ids\\\": [], \\\"instruction\\\": \\\"import pandas and load the dataset from 'test.csv'.\\\", \\\"task_type\\\": \\\"\\\", \\\"code\\\": \\\"\\\", \\\"result\\\": \\\"\\\", \\\"is_finished\\\": false}\\n \"}, {\"role\": \"assistant\", \"content\": \"import pandas as pd\\ndata = pd.read_excel('test.csv')\\ndata\"}, {\"role\": \"user\", \"content\": \"\\n Traceback (most recent call last):\\n File \\\"\\\", line 2, in \\n File \\\"/Users/gary/miniconda3/envs/py39_scratch/lib/python3.9/site-packages/pandas/io/excel/_base.py\\\", line 478, in read_excel\\n io = ExcelFile(io, storage_options=storage_options, engine=engine)\\n File \\\"/Users/gary/miniconda3/envs/py39_scratch/lib/python3.9/site-packages/pandas/io/excel/_base.py\\\", line 1500, in __init__\\n raise ValueError(\\n ValueError: Excel file format cannot be determined, you must specify an engine manually.\\n \"}]": {
+ "language": "python",
+ "code": "import pandas as pd\ndata = pd.read_csv('test.csv')"
+ },
+ "[{\"role\": \"system\", \"content\": \"You are Code Interpreter, a world-class programmer that can complete any goal by executing code. Strictly follow the plan and generate code step by step. Each step of the code will be executed on the user's machine, and the user will provide the code execution results to you.**Notice: The code for the next step depends on the code for the previous step. Must reuse variables in the lastest other code directly, dont creat it again, it is very import for you. Use !pip install in a standalone block to install missing packages.Usually the libraries you need are already installed.Dont check if packages already imported.**\"}, {\"role\": \"user\", \"content\": \"\\n ## User Requirement\\n read a dataset test.csv and print its head\\n ## Current Plan\\n [\\n {\\n \\\"task_id\\\": \\\"1\\\",\\n \\\"dependent_task_ids\\\": [],\\n \\\"instruction\\\": \\\"import pandas and load the dataset from 'test.csv'.\\\",\\n \\\"task_type\\\": \\\"\\\",\\n \\\"code\\\": \\\"import pandas as pd\\ndata = pd.read_csv('test.csv')\\\",\\n \\\"result\\\": \\\"\\\",\\n \\\"is_finished\\\": true\\n },\\n {\\n \\\"task_id\\\": \\\"2\\\",\\n \\\"dependent_task_ids\\\": [\\n \\\"1\\\"\\n ],\\n \\\"instruction\\\": \\\"Print the head of the dataset to display the first few rows.\\\",\\n \\\"task_type\\\": \\\"\\\",\\n \\\"code\\\": \\\"\\\",\\n \\\"result\\\": \\\"\\\",\\n \\\"is_finished\\\": false\\n }\\n ]\\n ## Current Task\\n {\\\"task_id\\\": \\\"2\\\", \\\"dependent_task_ids\\\": [\\\"1\\\"], \\\"instruction\\\": \\\"Print the head of the dataset to display the first few rows.\\\", \\\"task_type\\\": \\\"\\\", \\\"code\\\": \\\"\\\", \\\"result\\\": \\\"\\\", \\\"is_finished\\\": false}\\n \"}]": {
+ "language": "python",
+ "code": "print(data.head())"
+ },
+ "[{\"role\": \"system\", \"content\": \"You are an AI Python assistant. You will be given your previous implementation code of a task, runtime error results, and a hint to change the implementation appropriately. Write your full implementation \"}, {\"role\": \"user\", \"content\": \"\\nHere is an example for you.\\n\\nExample 1:\\n[previous impl]:\\n```python\\ndef add(a: int, b: int) -> int:\\n \\\"\\\"\\\"\\n Given integers a and b, return the total value of a and b.\\n \\\"\\\"\\\"\\n return a - b\\n```\\n\\n[runtime Error]:\\nTested passed:\\n\\nTests failed:\\nassert add(1, 2) == 3 # output: -1\\nassert add(1, 2) == 4 # output: -1\\n\\n[reflection on previous impl]:\\nThe implementation failed the test cases where the input integers are 1 and 2. The issue arises because the code does not add the two integers together, but instead subtracts the second integer from the first. To fix this issue, we should change the operator from `-` to `+` in the return statement. This will ensure that the function returns the correct output for the given input.\\n\\n[improved impl]:\\n```python\\ndef add(a: int, b: int) -> int:\\n \\\"\\\"\\\"\\n Given integers a and b, return the total value of a and b.\\n \\\"\\\"\\\"\\n return a + b\\n```\\n\\n[context]\\nuser: Solve the problem in Python:\\ndef sort_array(arr):\\n \\\"\\\"\\\"\\n In this Kata, you have to sort an array of non-negative integers according to\\n number of ones in their binary representation in ascending order.\\n For similar number of ones, sort based on decimal value.\\n\\n It must be implemented like this:\\n >>> sort_array([1, 5, 2, 3, 4]) == [1, 2, 3, 4, 5]\\n >>> sort_array([-2, -3, -4, -5, -6]) == [-6, -5, -4, -3, -2]\\n >>> sort_array([1, 0, 2, 3, 4]) [0, 1, 2, 3, 4]\\n \\\"\\\"\\\"\\n\\n\\n[previous impl]\\n\\ndef sort_array(arr):\\n # Helper function to count the number of ones in the binary representation\\n def count_ones(n):\\n return bin(n).count('1')\\n \\n # Sort the array using a custom key function\\n # The key function returns a tuple (number of ones, value) for each element\\n # This ensures that if two elements have the same number of ones, they are sorted by their value\\n sorted_arr = sorted(arr, key=lambda x: (count_ones(x), x))\\n \\n return sorted_arr\\n```\\n\\n[runtime Error]\\nTested passed:\\n\\nTests failed:\\nassert sort_array([1, 5, 2, 3, 4]) == [1, 2, 3, 4, 5] # output: [1, 2, 4, 3, 5]\\n\\n\\nAnalysis the error step by step, provide me improve method and code. Remember to follow [context] rerquirement. Don't forget write code for steps behind the error step.\\n[reflection on previous impl]:\\nxxx\\n\"}]": {
+ "reflection": "The implementation failed the test case where the input array is [1, 5, 2, 3, 4]. The issue arises because the code does not handle negative numbers correctly, as the binary representation of negative numbers in Python includes a minus sign, which is not considered in the count_ones function. To fix this issue, we should modify the count_ones function to handle negative numbers appropriately by ensuring that it only counts the '1's in the binary representation of the absolute value of the number. This will ensure that the function returns the correct output for both positive and negative numbers.",
+ "improved_impl": "def sort_array(arr):\n # Helper function to count the number of ones in the binary representation\n def count_ones(n):\n return bin(abs(n)).count('1')\n \n # Sort the array using a custom key function\n # The key function returns a tuple (number of ones, value) for each element\n # This ensures that if two elements have the same number of ones, they are sorted by their value\n sorted_arr = sorted(arr, key=lambda x: (count_ones(x), x))\n \n return sorted_arr"
+ },
+ "Do not refer to the context of the previous conversation records, start the conversation anew.\n\nFormation: \"Capacity and role\" defines the role you are currently playing;\n\t\"[LESSON_BEGIN]\" and \"[LESSON_END]\" tags enclose the content of textbook;\n\t\"Statement\" defines the work detail you need to complete at this stage;\n\t\"Answer options\" defines the format requirements for your responses;\n\t\"Constraint\" defines the conditions that your responses must comply with.\n\nCapacity and role: You are a {teaching_language} Teacher, named Lily, your goal is writing a {language} teaching plan part by part. the constraint is writing in {language}. \nStatement: Write the \"Teaching Hours\" part of teaching plan, WITHOUT ANY content unrelated to \"Teaching Hours\"!!\n\nAnswer options: Enclose the teaching plan content with \"[TEACHING_PLAN_BEGIN]\" and \"[TEACHING_PLAN_END]\" tags.\nAnswer options: Using proper markdown format from second-level header format.\nConstraint: Writing in Chinese.\n[LESSON_BEGIN]\n\n UNIT 1 Making New Friends\n TOPIC 1 Welcome to China!\n Section A\n\n 1a Listen and number the following names.\n Jane Mari Kangkang Michael\n Look, listen and understand. Then practice the conversation.\n Work in groups. Introduce yourself using\n I ’m ... Then practice 1a\n with your own hometown or the following places.\n\n 1b Listen and number the following names\n Jane Michael Maria Kangkang\n 1c Work in groups. Introduce yourself using I ’m ... Then practice 1a with your own hometown or the following places.\n China the USA the UK Hong Kong Beijing\n\n 2a Look, listen and understand. Then practice the conversation\n Hello! \n Hello! \n Hello! \n Hello! Are you Maria? \n No, I’m not. I’m Jane.\n Oh, nice to meet you, Jane\n Nice to meet you, too.\n Hi, Maria!\n Hi, Kangkang!\n Welcome to China!\n Thanks.\n\n 2b Work in groups. Make up a conversation with your own name and the\n following structures.\n A: Hello! / Good morning! / Hi! I’m ... Are you ... ?\n B: ...\n\n 3a Listen, say and trace\n Aa Bb Cc Dd Ee Ff Gg\n\n 3b Listen and number the following letters. Then circle the letters with the same sound as Bb.\n Aa Bb Cc Dd Ee Ff Gg\n\n 3c Match the big letters with the small ones. Then write them on the lines.\n \n[LESSON_END]": "[TEACHING_PLAN_BEGIN]\n\n## Teaching Hours\n\n本教学单元共包括 4 课时,每课时 45 分钟。\n\n### 课时安排\n\n- 第一课时:1a 和 1b 部分\n- 第二课时:1c 和 2a 部分\n- 第三课时:2b 和 3a 部分\n- 第四课时:3b 和 3c 部分\n\n[TEACHING_PLAN_END]",
+ "Do not refer to the context of the previous conversation records, start the conversation anew.\n\nFormation: \"Capacity and role\" defines the role you are currently playing;\n\t\"[LESSON_BEGIN]\" and \"[LESSON_END]\" tags enclose the content of textbook;\n\t\"Statement\" defines the work detail you need to complete at this stage;\n\t\"Answer options\" defines the format requirements for your responses;\n\t\"Constraint\" defines the conditions that your responses must comply with.\n\nCapacity and role: You are a {teaching_language} Teacher, named Lily, your goal is writing a {language} teaching plan part by part. the constraint is writing in {language}. \nStatement: Write the \"Teaching Objectives\" part of teaching plan, WITHOUT ANY content unrelated to \"Teaching Objectives\"!!\n\nAnswer options: Enclose the teaching plan content with \"[TEACHING_PLAN_BEGIN]\" and \"[TEACHING_PLAN_END]\" tags.\nAnswer options: Using proper markdown format from second-level header format.\nConstraint: Writing in Chinese.\n[LESSON_BEGIN]\n\n UNIT 1 Making New Friends\n TOPIC 1 Welcome to China!\n Section A\n\n 1a Listen and number the following names.\n Jane Mari Kangkang Michael\n Look, listen and understand. Then practice the conversation.\n Work in groups. Introduce yourself using\n I ’m ... Then practice 1a\n with your own hometown or the following places.\n\n 1b Listen and number the following names\n Jane Michael Maria Kangkang\n 1c Work in groups. Introduce yourself using I ’m ... Then practice 1a with your own hometown or the following places.\n China the USA the UK Hong Kong Beijing\n\n 2a Look, listen and understand. Then practice the conversation\n Hello! \n Hello! \n Hello! \n Hello! Are you Maria? \n No, I’m not. I’m Jane.\n Oh, nice to meet you, Jane\n Nice to meet you, too.\n Hi, Maria!\n Hi, Kangkang!\n Welcome to China!\n Thanks.\n\n 2b Work in groups. Make up a conversation with your own name and the\n following structures.\n A: Hello! / Good morning! / Hi! I’m ... Are you ... ?\n B: ...\n\n 3a Listen, say and trace\n Aa Bb Cc Dd Ee Ff Gg\n\n 3b Listen and number the following letters. Then circle the letters with the same sound as Bb.\n Aa Bb Cc Dd Ee Ff Gg\n\n 3c Match the big letters with the small ones. Then write them on the lines.\n \n[LESSON_END]": "[TEACHING_PLAN_BEGIN]\n\n## Teaching Objectives\n\n1. Students will be able to listen and understand the names of different people in English.\n2. Students will be able to introduce themselves using the correct structure \"I'm [name]\".\n3. Students will be able to engage in simple conversational exchanges using greetings and introductions.\n4. Students will be able to recognize and match big and small letters in the English alphabet.\n\n[TEACHING_PLAN_END]",
+ "Do not refer to the context of the previous conversation records, start the conversation anew.\n\nFormation: \"Capacity and role\" defines the role you are currently playing;\n\t\"[LESSON_BEGIN]\" and \"[LESSON_END]\" tags enclose the content of textbook;\n\t\"Statement\" defines the work detail you need to complete at this stage;\n\t\"Answer options\" defines the format requirements for your responses;\n\t\"Constraint\" defines the conditions that your responses must comply with.\n\nCapacity and role: You are a {teaching_language} Teacher, named Lily, your goal is writing a {language} teaching plan part by part. the constraint is writing in {language}. \nStatement: Write the \"Teaching Content\" part of teaching plan, WITHOUT ANY content unrelated to \"Teaching Content\"!!\nStatement: \"Teaching Content\" must include vocabulary, analysis, and examples of various grammar structures that appear in the textbook, as well as the listening materials and key points.\nStatement: \"Teaching Content\" must include more examples.\nAnswer options: Enclose the teaching plan content with \"[TEACHING_PLAN_BEGIN]\" and \"[TEACHING_PLAN_END]\" tags.\nAnswer options: Using proper markdown format from second-level header format.\nConstraint: Writing in Chinese.\n[LESSON_BEGIN]\n\n UNIT 1 Making New Friends\n TOPIC 1 Welcome to China!\n Section A\n\n 1a Listen and number the following names.\n Jane Mari Kangkang Michael\n Look, listen and understand. Then practice the conversation.\n Work in groups. Introduce yourself using\n I ’m ... Then practice 1a\n with your own hometown or the following places.\n\n 1b Listen and number the following names\n Jane Michael Maria Kangkang\n 1c Work in groups. Introduce yourself using I ’m ... Then practice 1a with your own hometown or the following places.\n China the USA the UK Hong Kong Beijing\n\n 2a Look, listen and understand. Then practice the conversation\n Hello! \n Hello! \n Hello! \n Hello! Are you Maria? \n No, I’m not. I’m Jane.\n Oh, nice to meet you, Jane\n Nice to meet you, too.\n Hi, Maria!\n Hi, Kangkang!\n Welcome to China!\n Thanks.\n\n 2b Work in groups. Make up a conversation with your own name and the\n following structures.\n A: Hello! / Good morning! / Hi! I’m ... Are you ... ?\n B: ...\n\n 3a Listen, say and trace\n Aa Bb Cc Dd Ee Ff Gg\n\n 3b Listen and number the following letters. Then circle the letters with the same sound as Bb.\n Aa Bb Cc Dd Ee Ff Gg\n\n 3c Match the big letters with the small ones. Then write them on the lines.\n \n[LESSON_END]": "[TEACHING_PLAN_BEGIN]\n\n## 教学内容\n\n### 词汇\n- 名字:Jane, Mari, Kangkang, Michael, Maria\n- 地点:China, the USA, the UK, Hong Kong, Beijing\n\n### 语法分析\n- 介绍自己的句式:I’m ...\n- 问候句式:Hello! / Good morning! / Hi! I’m ... Are you ... ?\n- 回答问候的句式:No, I’m not. I’m Jane. / Oh, nice to meet you, Jane. / Nice to meet you, too. / Hi, Maria! / Hi, Kangkang! / Welcome to China! / Thanks.\n\n### 例句\n- 例句1:Hello! Are you Maria? No, I’m not. I’m Jane.\n- 例句2:Hi, Maria! Hi, Kangkang! Welcome to China! Thanks.\n\n### 听力材料\n- 听力练习1a、1b、2a、3a\n\n### 关键点\n- 学生能够用英语介绍自己的名字和来自的地方\n- 学生能够用正确的问候方式和回答方式进行交流\n\n[TEACHING_PLAN_END]",
+ "Do not refer to the context of the previous conversation records, start the conversation anew.\n\nFormation: \"Capacity and role\" defines the role you are currently playing;\n\t\"[LESSON_BEGIN]\" and \"[LESSON_END]\" tags enclose the content of textbook;\n\t\"Statement\" defines the work detail you need to complete at this stage;\n\t\"Answer options\" defines the format requirements for your responses;\n\t\"Constraint\" defines the conditions that your responses must comply with.\n\nCapacity and role: You are a {teaching_language} Teacher, named Lily, your goal is writing a {language} teaching plan part by part. the constraint is writing in {language}. \nStatement: Write the \"Teaching Methods and Strategies\" part of teaching plan, WITHOUT ANY content unrelated to \"Teaching Methods and Strategies\"!!\nStatement: \"Teaching Methods and Strategies\" must include teaching focus, difficulties, materials, procedures, in detail.\nAnswer options: Enclose the teaching plan content with \"[TEACHING_PLAN_BEGIN]\" and \"[TEACHING_PLAN_END]\" tags.\nAnswer options: Using proper markdown format from second-level header format.\nConstraint: Writing in Chinese.\n[LESSON_BEGIN]\n\n UNIT 1 Making New Friends\n TOPIC 1 Welcome to China!\n Section A\n\n 1a Listen and number the following names.\n Jane Mari Kangkang Michael\n Look, listen and understand. Then practice the conversation.\n Work in groups. Introduce yourself using\n I ’m ... Then practice 1a\n with your own hometown or the following places.\n\n 1b Listen and number the following names\n Jane Michael Maria Kangkang\n 1c Work in groups. Introduce yourself using I ’m ... Then practice 1a with your own hometown or the following places.\n China the USA the UK Hong Kong Beijing\n\n 2a Look, listen and understand. Then practice the conversation\n Hello! \n Hello! \n Hello! \n Hello! Are you Maria? \n No, I’m not. I’m Jane.\n Oh, nice to meet you, Jane\n Nice to meet you, too.\n Hi, Maria!\n Hi, Kangkang!\n Welcome to China!\n Thanks.\n\n 2b Work in groups. Make up a conversation with your own name and the\n following structures.\n A: Hello! / Good morning! / Hi! I’m ... Are you ... ?\n B: ...\n\n 3a Listen, say and trace\n Aa Bb Cc Dd Ee Ff Gg\n\n 3b Listen and number the following letters. Then circle the letters with the same sound as Bb.\n Aa Bb Cc Dd Ee Ff Gg\n\n 3c Match the big letters with the small ones. Then write them on the lines.\n \n[LESSON_END]": "[TEACHING_PLAN_BEGIN]\n\n## Teaching Methods and Strategies\n\n### Teaching Focus\n- Introducing the topic \"Making New Friends\" and \"Welcome to China!\"\n- Engaging students in listening and speaking activities to practice conversation skills\n- Encouraging group work and interaction among students\n\n### Difficulties\n- Ensuring that students understand and remember the new vocabulary and sentence structures\n- Encouraging shy or hesitant students to actively participate in group activities\n\n### Materials\n- Audio recordings for listening exercises\n- Visual aids such as flashcards or images related to the topic\n- Worksheets for practice activities\n- Name tags for students to use during role-playing activities\n\n### Procedures\n1. **Introduction**\n - Begin the lesson by discussing the importance of making new friends and the cultural aspects of welcoming someone to a new place.\n - Use visual aids and real-life examples to engage students in the topic.\n\n2. **Listening and Speaking Activities**\n - Play the audio recordings for the listening exercises and have students participate in number and name matching activities.\n - Encourage students to practice the conversation structures in pairs or small groups, using their own names and the given structures.\n\n3. **Group Role-Playing**\n - Divide the class into groups and assign each group a scenario to role-play, incorporating the structures learned in the lesson.\n - Monitor and provide feedback to each group, encouraging active participation and fluency in spoken English.\n\n4. **Letter Recognition**\n - Introduce the letters and their corresponding sounds through interactive activities such as tracing, matching, and writing exercises.\n - Provide additional practice and reinforcement for students who may struggle with letter recognition.\n\n5. **Conclusion**\n - Summarize the key points of the lesson and encourage students to reflect on their learning experiences.\n - Assign homework or additional practice exercises related to the lesson content.\n\n[TEACHING_PLAN_END]",
+ "Do not refer to the context of the previous conversation records, start the conversation anew.\n\nFormation: \"Capacity and role\" defines the role you are currently playing;\n\t\"[LESSON_BEGIN]\" and \"[LESSON_END]\" tags enclose the content of textbook;\n\t\"Statement\" defines the work detail you need to complete at this stage;\n\t\"Answer options\" defines the format requirements for your responses;\n\t\"Constraint\" defines the conditions that your responses must comply with.\n\nCapacity and role: You are a {teaching_language} Teacher, named Lily, your goal is writing a {language} teaching plan part by part. the constraint is writing in {language}. \nStatement: Write the \"Learning Activities\" part of teaching plan, WITHOUT ANY content unrelated to \"Learning Activities\"!!\n\nAnswer options: Enclose the teaching plan content with \"[TEACHING_PLAN_BEGIN]\" and \"[TEACHING_PLAN_END]\" tags.\nAnswer options: Using proper markdown format from second-level header format.\nConstraint: Writing in Chinese.\n[LESSON_BEGIN]\n\n UNIT 1 Making New Friends\n TOPIC 1 Welcome to China!\n Section A\n\n 1a Listen and number the following names.\n Jane Mari Kangkang Michael\n Look, listen and understand. Then practice the conversation.\n Work in groups. Introduce yourself using\n I ’m ... Then practice 1a\n with your own hometown or the following places.\n\n 1b Listen and number the following names\n Jane Michael Maria Kangkang\n 1c Work in groups. Introduce yourself using I ’m ... Then practice 1a with your own hometown or the following places.\n China the USA the UK Hong Kong Beijing\n\n 2a Look, listen and understand. Then practice the conversation\n Hello! \n Hello! \n Hello! \n Hello! Are you Maria? \n No, I’m not. I’m Jane.\n Oh, nice to meet you, Jane\n Nice to meet you, too.\n Hi, Maria!\n Hi, Kangkang!\n Welcome to China!\n Thanks.\n\n 2b Work in groups. Make up a conversation with your own name and the\n following structures.\n A: Hello! / Good morning! / Hi! I’m ... Are you ... ?\n B: ...\n\n 3a Listen, say and trace\n Aa Bb Cc Dd Ee Ff Gg\n\n 3b Listen and number the following letters. Then circle the letters with the same sound as Bb.\n Aa Bb Cc Dd Ee Ff Gg\n\n 3c Match the big letters with the small ones. Then write them on the lines.\n \n[LESSON_END]": "[TEACHING_PLAN_BEGIN]\n\n## 学习活动\n\n- 学生分组练习\n - 学生分组,使用\"I'm...\"自我介绍,然后练习1a,谈论自己的家乡或指定的地点。\n- 听力练习\n - 学生听力练习,听音标号姓名,然后分组练习自我介绍和谈论地点。\n- 对话练习\n - 学生分组练习对话,使用指定的结构进行对话练习。\n- 字母练习\n - 学生听力练习,听音标号字母,然后进行字母匹配和书写练习。\n\n[TEACHING_PLAN_END]",
+ "Do not refer to the context of the previous conversation records, start the conversation anew.\n\nFormation: \"Capacity and role\" defines the role you are currently playing;\n\t\"[LESSON_BEGIN]\" and \"[LESSON_END]\" tags enclose the content of textbook;\n\t\"Statement\" defines the work detail you need to complete at this stage;\n\t\"Answer options\" defines the format requirements for your responses;\n\t\"Constraint\" defines the conditions that your responses must comply with.\n\nCapacity and role: You are a {teaching_language} Teacher, named Lily, your goal is writing a {language} teaching plan part by part. the constraint is writing in {language}. \nStatement: Write the \"Teaching Time Allocation\" part of teaching plan, WITHOUT ANY content unrelated to \"Teaching Time Allocation\"!!\nStatement: \"Teaching Time Allocation\" must include how much time is allocated to each part of the textbook content.\nAnswer options: Enclose the teaching plan content with \"[TEACHING_PLAN_BEGIN]\" and \"[TEACHING_PLAN_END]\" tags.\nAnswer options: Using proper markdown format from second-level header format.\nConstraint: Writing in Chinese.\n[LESSON_BEGIN]\n\n UNIT 1 Making New Friends\n TOPIC 1 Welcome to China!\n Section A\n\n 1a Listen and number the following names.\n Jane Mari Kangkang Michael\n Look, listen and understand. Then practice the conversation.\n Work in groups. Introduce yourself using\n I ’m ... Then practice 1a\n with your own hometown or the following places.\n\n 1b Listen and number the following names\n Jane Michael Maria Kangkang\n 1c Work in groups. Introduce yourself using I ’m ... Then practice 1a with your own hometown or the following places.\n China the USA the UK Hong Kong Beijing\n\n 2a Look, listen and understand. Then practice the conversation\n Hello! \n Hello! \n Hello! \n Hello! Are you Maria? \n No, I’m not. I’m Jane.\n Oh, nice to meet you, Jane\n Nice to meet you, too.\n Hi, Maria!\n Hi, Kangkang!\n Welcome to China!\n Thanks.\n\n 2b Work in groups. Make up a conversation with your own name and the\n following structures.\n A: Hello! / Good morning! / Hi! I’m ... Are you ... ?\n B: ...\n\n 3a Listen, say and trace\n Aa Bb Cc Dd Ee Ff Gg\n\n 3b Listen and number the following letters. Then circle the letters with the same sound as Bb.\n Aa Bb Cc Dd Ee Ff Gg\n\n 3c Match the big letters with the small ones. Then write them on the lines.\n \n[LESSON_END]": "[TEACHING_PLAN_BEGIN]\n\n## Teaching Time Allocation\n\n- 1a Listen and number the following names: 10 minutes\n- 1b Listen and number the following names: 10 minutes\n- 1c Work in groups: 15 minutes\n- 2a Look, listen and understand: 10 minutes\n- 2b Work in groups: 15 minutes\n- 3a Listen, say and trace: 10 minutes\n- 3b Listen and number the following letters: 10 minutes\n- 3c Match the big letters with the small ones: 10 minutes\n\n[TEACHING_PLAN_END]",
+ "Do not refer to the context of the previous conversation records, start the conversation anew.\n\nFormation: \"Capacity and role\" defines the role you are currently playing;\n\t\"[LESSON_BEGIN]\" and \"[LESSON_END]\" tags enclose the content of textbook;\n\t\"Statement\" defines the work detail you need to complete at this stage;\n\t\"Answer options\" defines the format requirements for your responses;\n\t\"Constraint\" defines the conditions that your responses must comply with.\n\nCapacity and role: You are a {teaching_language} Teacher, named Lily, your goal is writing a {language} teaching plan part by part. the constraint is writing in {language}. \nStatement: Write the \"Assessment and Feedback\" part of teaching plan, WITHOUT ANY content unrelated to \"Assessment and Feedback\"!!\n\nAnswer options: Enclose the teaching plan content with \"[TEACHING_PLAN_BEGIN]\" and \"[TEACHING_PLAN_END]\" tags.\nAnswer options: Using proper markdown format from second-level header format.\nConstraint: Writing in Chinese.\n[LESSON_BEGIN]\n\n UNIT 1 Making New Friends\n TOPIC 1 Welcome to China!\n Section A\n\n 1a Listen and number the following names.\n Jane Mari Kangkang Michael\n Look, listen and understand. Then practice the conversation.\n Work in groups. Introduce yourself using\n I ’m ... Then practice 1a\n with your own hometown or the following places.\n\n 1b Listen and number the following names\n Jane Michael Maria Kangkang\n 1c Work in groups. Introduce yourself using I ’m ... Then practice 1a with your own hometown or the following places.\n China the USA the UK Hong Kong Beijing\n\n 2a Look, listen and understand. Then practice the conversation\n Hello! \n Hello! \n Hello! \n Hello! Are you Maria? \n No, I’m not. I’m Jane.\n Oh, nice to meet you, Jane\n Nice to meet you, too.\n Hi, Maria!\n Hi, Kangkang!\n Welcome to China!\n Thanks.\n\n 2b Work in groups. Make up a conversation with your own name and the\n following structures.\n A: Hello! / Good morning! / Hi! I’m ... Are you ... ?\n B: ...\n\n 3a Listen, say and trace\n Aa Bb Cc Dd Ee Ff Gg\n\n 3b Listen and number the following letters. Then circle the letters with the same sound as Bb.\n Aa Bb Cc Dd Ee Ff Gg\n\n 3c Match the big letters with the small ones. Then write them on the lines.\n \n[LESSON_END]": "[TEACHING_PLAN_BEGIN]\n\n## 评估与反馈\n\n在教学过程中,我们需要对学生的学习情况进行评估和反馈,以便及时发现问题并加以解决。评估和反馈的方式可以包括以下几种:\n\n1. **口头评估**:通过课堂互动、小组讨论等方式,对学生的口语表达能力进行评估,并及时给予反馈和指导。\n\n2. **书面评估**:布置书面作业、小测验等形式,对学生的书面表达能力和语法运用进行评估,并在批改作业时给予详细的反馈。\n\n3. **个性化反馈**:针对学生个体差异,及时给予个性化的学习建议和反馈,帮助学生克服困难,提高学习效果。\n\n通过以上评估和反馈方式,可以更全面地了解学生的学习情况,帮助他们更好地掌握课程内容,提高语言表达能力。\n\n[TEACHING_PLAN_END]",
+ "Do not refer to the context of the previous conversation records, start the conversation anew.\n\nFormation: \"Capacity and role\" defines the role you are currently playing;\n\t\"[LESSON_BEGIN]\" and \"[LESSON_END]\" tags enclose the content of textbook;\n\t\"Statement\" defines the work detail you need to complete at this stage;\n\t\"Answer options\" defines the format requirements for your responses;\n\t\"Constraint\" defines the conditions that your responses must comply with.\n\nCapacity and role: You are a {teaching_language} Teacher, named Lily, your goal is writing a {language} teaching plan part by part. the constraint is writing in {language}. \nStatement: Write the \"Teaching Summary and Improvement\" part of teaching plan, WITHOUT ANY content unrelated to \"Teaching Summary and Improvement\"!!\n\nAnswer options: Enclose the teaching plan content with \"[TEACHING_PLAN_BEGIN]\" and \"[TEACHING_PLAN_END]\" tags.\nAnswer options: Using proper markdown format from second-level header format.\nConstraint: Writing in Chinese.\n[LESSON_BEGIN]\n\n UNIT 1 Making New Friends\n TOPIC 1 Welcome to China!\n Section A\n\n 1a Listen and number the following names.\n Jane Mari Kangkang Michael\n Look, listen and understand. Then practice the conversation.\n Work in groups. Introduce yourself using\n I ’m ... Then practice 1a\n with your own hometown or the following places.\n\n 1b Listen and number the following names\n Jane Michael Maria Kangkang\n 1c Work in groups. Introduce yourself using I ’m ... Then practice 1a with your own hometown or the following places.\n China the USA the UK Hong Kong Beijing\n\n 2a Look, listen and understand. Then practice the conversation\n Hello! \n Hello! \n Hello! \n Hello! Are you Maria? \n No, I’m not. I’m Jane.\n Oh, nice to meet you, Jane\n Nice to meet you, too.\n Hi, Maria!\n Hi, Kangkang!\n Welcome to China!\n Thanks.\n\n 2b Work in groups. Make up a conversation with your own name and the\n following structures.\n A: Hello! / Good morning! / Hi! I’m ... Are you ... ?\n B: ...\n\n 3a Listen, say and trace\n Aa Bb Cc Dd Ee Ff Gg\n\n 3b Listen and number the following letters. Then circle the letters with the same sound as Bb.\n Aa Bb Cc Dd Ee Ff Gg\n\n 3c Match the big letters with the small ones. Then write them on the lines.\n \n[LESSON_END]": "[TEACHING_PLAN_BEGIN]\n\n## Teaching Summary and Improvement\n\nIn this unit, the focus was on introducing oneself and making new friends. The students practiced listening and speaking skills through various activities such as listening to conversations, introducing themselves, and matching letters. The unit aimed to improve the students' communication skills and confidence in using the language.\n\nTo improve the teaching effectiveness, more interactive activities can be incorporated to encourage students to engage in real-life conversations. Additionally, providing opportunities for students to apply the language in practical scenarios, such as role-playing situations, can enhance their language acquisition and confidence.\n\n[TEACHING_PLAN_END]",
+ "Do not refer to the context of the previous conversation records, start the conversation anew.\n\nFormation: \"Capacity and role\" defines the role you are currently playing;\n\t\"[LESSON_BEGIN]\" and \"[LESSON_END]\" tags enclose the content of textbook;\n\t\"Statement\" defines the work detail you need to complete at this stage;\n\t\"Answer options\" defines the format requirements for your responses;\n\t\"Constraint\" defines the conditions that your responses must comply with.\n\nCapacity and role: You are a {teaching_language} Teacher, named Lily, your goal is writing a {language} teaching plan part by part. the constraint is writing in {language}. \nStatement: Write the \"Vocabulary Cloze\" part of teaching plan, WITHOUT ANY content unrelated to \"Vocabulary Cloze\"!!\nStatement: Based on the content of the textbook enclosed by \"[LESSON_BEGIN]\" and \"[LESSON_END]\", create vocabulary cloze. The cloze should include 10 {language} questions with {teaching_language} answers, and it should also include 10 {teaching_language} questions with {language} answers. The key-related vocabulary and phrases in the textbook content must all be included in the exercises.\nAnswer options: Enclose the teaching plan content with \"[TEACHING_PLAN_BEGIN]\" and \"[TEACHING_PLAN_END]\" tags.\nAnswer options: Using proper markdown format from second-level header format.\nConstraint: Writing in Chinese.\n[LESSON_BEGIN]\n\n UNIT 1 Making New Friends\n TOPIC 1 Welcome to China!\n Section A\n\n 1a Listen and number the following names.\n Jane Mari Kangkang Michael\n Look, listen and understand. Then practice the conversation.\n Work in groups. Introduce yourself using\n I ’m ... Then practice 1a\n with your own hometown or the following places.\n\n 1b Listen and number the following names\n Jane Michael Maria Kangkang\n 1c Work in groups. Introduce yourself using I ’m ... Then practice 1a with your own hometown or the following places.\n China the USA the UK Hong Kong Beijing\n\n 2a Look, listen and understand. Then practice the conversation\n Hello! \n Hello! \n Hello! \n Hello! Are you Maria? \n No, I’m not. I’m Jane.\n Oh, nice to meet you, Jane\n Nice to meet you, too.\n Hi, Maria!\n Hi, Kangkang!\n Welcome to China!\n Thanks.\n\n 2b Work in groups. Make up a conversation with your own name and the\n following structures.\n A: Hello! / Good morning! / Hi! I’m ... Are you ... ?\n B: ...\n\n 3a Listen, say and trace\n Aa Bb Cc Dd Ee Ff Gg\n\n 3b Listen and number the following letters. Then circle the letters with the same sound as Bb.\n Aa Bb Cc Dd Ee Ff Gg\n\n 3c Match the big letters with the small ones. Then write them on the lines.\n \n[LESSON_END]": "[TEACHING_PLAN_BEGIN]\n\n## 词汇填空\n\n### 语言填空\n1. 你好! 你是玛丽亚吗?\n - 不,我不是。我是简。\n - 哦,很高兴认识你,简。\n - 我也很高兴认识你。\n\n2. 你好! / 早上好! / 嗨! 我是... 你是...吗?\n - ...\n\n3. Aa Bb Cc Dd Ee Ff Gg\n\n4. 匹配大写字母和小写字母。然后在线上写出它们。\n\n5. ...\n\n### {language}填空\n1. 1a 听录音,给以下名字编号。\n - 简 玛丽 康康 迈克尔\n\n2. 1b 听录音,给以下名字编号。\n - 简 迈克尔 玛丽亚 康康\n\n3. 1c 分组活动。使用“我是...”介绍自己。然后用你自己的家乡或以下地方练习1a。\n - 中国 美国 英国 香港 北京\n\n4. 2a 看,听,理解。然后练习对话。\n - 你好!\n - 你好!\n - 你好!\n - 你好! 你是玛丽亚吗?\n - 不,我不是。我是简。\n - 哦,很高兴认识你,简。\n - 我也很高兴认识你。\n - 你好,玛丽亚!\n - 你好,康康!\n - 欢迎来到中国!\n - 谢谢。\n\n5. 2b 分组活动。编写一个包含你自己名字和以下结构的对话。\n - A: 你好! / 早上好! / 嗨! 我是... 你是...吗?\n - B: ...\n\n[TEACHING_PLAN_END]",
+ "Do not refer to the context of the previous conversation records, start the conversation anew.\n\nFormation: \"Capacity and role\" defines the role you are currently playing;\n\t\"[LESSON_BEGIN]\" and \"[LESSON_END]\" tags enclose the content of textbook;\n\t\"Statement\" defines the work detail you need to complete at this stage;\n\t\"Answer options\" defines the format requirements for your responses;\n\t\"Constraint\" defines the conditions that your responses must comply with.\n\nCapacity and role: You are a {teaching_language} Teacher, named Lily, your goal is writing a {language} teaching plan part by part. the constraint is writing in {language}. \nStatement: Write the \"Choice Questions\" part of teaching plan, WITHOUT ANY content unrelated to \"Choice Questions\"!!\nStatement: Based on the content of the textbook enclosed by \"[LESSON_BEGIN]\" and \"[LESSON_END]\", create choice questions. 10 questions.\nAnswer options: Enclose the teaching plan content with \"[TEACHING_PLAN_BEGIN]\" and \"[TEACHING_PLAN_END]\" tags.\nAnswer options: Using proper markdown format from second-level header format.\nConstraint: Writing in Chinese.\n[LESSON_BEGIN]\n\n UNIT 1 Making New Friends\n TOPIC 1 Welcome to China!\n Section A\n\n 1a Listen and number the following names.\n Jane Mari Kangkang Michael\n Look, listen and understand. Then practice the conversation.\n Work in groups. Introduce yourself using\n I ’m ... Then practice 1a\n with your own hometown or the following places.\n\n 1b Listen and number the following names\n Jane Michael Maria Kangkang\n 1c Work in groups. Introduce yourself using I ’m ... Then practice 1a with your own hometown or the following places.\n China the USA the UK Hong Kong Beijing\n\n 2a Look, listen and understand. Then practice the conversation\n Hello! \n Hello! \n Hello! \n Hello! Are you Maria? \n No, I’m not. I’m Jane.\n Oh, nice to meet you, Jane\n Nice to meet you, too.\n Hi, Maria!\n Hi, Kangkang!\n Welcome to China!\n Thanks.\n\n 2b Work in groups. Make up a conversation with your own name and the\n following structures.\n A: Hello! / Good morning! / Hi! I’m ... Are you ... ?\n B: ...\n\n 3a Listen, say and trace\n Aa Bb Cc Dd Ee Ff Gg\n\n 3b Listen and number the following letters. Then circle the letters with the same sound as Bb.\n Aa Bb Cc Dd Ee Ff Gg\n\n 3c Match the big letters with the small ones. Then write them on the lines.\n \n[LESSON_END]": "[TEACHING_PLAN_BEGIN]\n\n## 选择题\n\n1. 在1a部分,听到的名字按顺序应该是?\n - A. Jane B. Mari C. Kangkang D. Michael\n\n2. 在1b部分,听到的名字按顺序应该是?\n - A. Jane B. Michael C. Maria D. Kangkang\n\n3. 在2a部分,对话中提到了哪些地方?\n - A. 中国 B. 美国 C. 英国 D. 香港\n\n4. 在3b部分,与Bb发音相同的字母是?\n - A. Aa B. Cc C. Dd D. Gg\n\n5. 在3c部分,大写字母和小写字母的正确匹配是?\n - A. Aa - a B. Bb - b C. Cc - c D. Dd - d\n\n6. 在1a部分,听到的名字按顺序应该是?\n - A. Jane B. Mari C. Kangkang D. Michael\n\n7. 在1b部分,听到的名字按顺序应该是?\n - A. Jane B. Michael C. Maria D. Kangkang\n\n8. 在2a部分,对话中提到了哪些地方?\n - A. 中国 B. 美国 C. 英国 D. 香港\n\n9. 在3b部分,与Bb发音相同的字母是?\n - A. Aa B. Cc C. Dd D. Gg\n\n10. 在3c部分,大写字母和小写字母的正确匹配是?\n - A. Aa - a B. Bb - b C. Cc - c D. Dd - d\n\n[TEACHING_PLAN_END]",
+ "Do not refer to the context of the previous conversation records, start the conversation anew.\n\nFormation: \"Capacity and role\" defines the role you are currently playing;\n\t\"[LESSON_BEGIN]\" and \"[LESSON_END]\" tags enclose the content of textbook;\n\t\"Statement\" defines the work detail you need to complete at this stage;\n\t\"Answer options\" defines the format requirements for your responses;\n\t\"Constraint\" defines the conditions that your responses must comply with.\n\nCapacity and role: You are a {teaching_language} Teacher, named Lily, your goal is writing a {language} teaching plan part by part. the constraint is writing in {language}. \nStatement: Write the \"Grammar Questions\" part of teaching plan, WITHOUT ANY content unrelated to \"Grammar Questions\"!!\nStatement: Based on the content of the textbook enclosed by \"[LESSON_BEGIN]\" and \"[LESSON_END]\", create grammar questions. 10 questions.\nAnswer options: Enclose the teaching plan content with \"[TEACHING_PLAN_BEGIN]\" and \"[TEACHING_PLAN_END]\" tags.\nAnswer options: Using proper markdown format from second-level header format.\nConstraint: Writing in Chinese.\n[LESSON_BEGIN]\n\n UNIT 1 Making New Friends\n TOPIC 1 Welcome to China!\n Section A\n\n 1a Listen and number the following names.\n Jane Mari Kangkang Michael\n Look, listen and understand. Then practice the conversation.\n Work in groups. Introduce yourself using\n I ’m ... Then practice 1a\n with your own hometown or the following places.\n\n 1b Listen and number the following names\n Jane Michael Maria Kangkang\n 1c Work in groups. Introduce yourself using I ’m ... Then practice 1a with your own hometown or the following places.\n China the USA the UK Hong Kong Beijing\n\n 2a Look, listen and understand. Then practice the conversation\n Hello! \n Hello! \n Hello! \n Hello! Are you Maria? \n No, I’m not. I’m Jane.\n Oh, nice to meet you, Jane\n Nice to meet you, too.\n Hi, Maria!\n Hi, Kangkang!\n Welcome to China!\n Thanks.\n\n 2b Work in groups. Make up a conversation with your own name and the\n following structures.\n A: Hello! / Good morning! / Hi! I’m ... Are you ... ?\n B: ...\n\n 3a Listen, say and trace\n Aa Bb Cc Dd Ee Ff Gg\n\n 3b Listen and number the following letters. Then circle the letters with the same sound as Bb.\n Aa Bb Cc Dd Ee Ff Gg\n\n 3c Match the big letters with the small ones. Then write them on the lines.\n \n[LESSON_END]": "[TEACHING_PLAN_BEGIN]\n\n## Grammar Questions\n\n1. 请问在1a中,学生需要做什么?\n2. 请问在1b中,学生需要做什么?\n3. 请问在2a中,学生需要做什么?\n4. 请问在2b中,学生需要做什么?\n5. 请问在3a中,学生需要做什么?\n6. 请问在3b中,学生需要做什么?\n7. 请问在3c中,学生需要做什么?\n8. 请问在1a中,学生需要听什么?\n9. 请问在2a中,学生需要看什么?\n10. 请问在3a中,学生需要说什么?\n\n[TEACHING_PLAN_END]",
+ "Do not refer to the context of the previous conversation records, start the conversation anew.\n\nFormation: \"Capacity and role\" defines the role you are currently playing;\n\t\"[LESSON_BEGIN]\" and \"[LESSON_END]\" tags enclose the content of textbook;\n\t\"Statement\" defines the work detail you need to complete at this stage;\n\t\"Answer options\" defines the format requirements for your responses;\n\t\"Constraint\" defines the conditions that your responses must comply with.\n\nCapacity and role: You are a {teaching_language} Teacher, named Lily, your goal is writing a {language} teaching plan part by part. the constraint is writing in {language}. \nStatement: Write the \"Translation Questions\" part of teaching plan, WITHOUT ANY content unrelated to \"Translation Questions\"!!\nStatement: Based on the content of the textbook enclosed by \"[LESSON_BEGIN]\" and \"[LESSON_END]\", create translation questions. The translation should include 10 {language} questions with {teaching_language} answers, and it should also include 10 {teaching_language} questions with {language} answers.\nAnswer options: Enclose the teaching plan content with \"[TEACHING_PLAN_BEGIN]\" and \"[TEACHING_PLAN_END]\" tags.\nAnswer options: Using proper markdown format from second-level header format.\nConstraint: Writing in Chinese.\n[LESSON_BEGIN]\n\n UNIT 1 Making New Friends\n TOPIC 1 Welcome to China!\n Section A\n\n 1a Listen and number the following names.\n Jane Mari Kangkang Michael\n Look, listen and understand. Then practice the conversation.\n Work in groups. Introduce yourself using\n I ’m ... Then practice 1a\n with your own hometown or the following places.\n\n 1b Listen and number the following names\n Jane Michael Maria Kangkang\n 1c Work in groups. Introduce yourself using I ’m ... Then practice 1a with your own hometown or the following places.\n China the USA the UK Hong Kong Beijing\n\n 2a Look, listen and understand. Then practice the conversation\n Hello! \n Hello! \n Hello! \n Hello! Are you Maria? \n No, I’m not. I’m Jane.\n Oh, nice to meet you, Jane\n Nice to meet you, too.\n Hi, Maria!\n Hi, Kangkang!\n Welcome to China!\n Thanks.\n\n 2b Work in groups. Make up a conversation with your own name and the\n following structures.\n A: Hello! / Good morning! / Hi! I’m ... Are you ... ?\n B: ...\n\n 3a Listen, say and trace\n Aa Bb Cc Dd Ee Ff Gg\n\n 3b Listen and number the following letters. Then circle the letters with the same sound as Bb.\n Aa Bb Cc Dd Ee Ff Gg\n\n 3c Match the big letters with the small ones. Then write them on the lines.\n \n[LESSON_END]": "[TEACHING_PLAN_BEGIN]\n\n## Translation Questions\n\n### {language} Questions with {teaching_language} Answers\n1. 你能听懂这些名字吗? (Can you understand these names?)\n - 能,我能听懂。 (Yes, I can understand.)\n2. 请用“我是...”介绍一下你自己。 (Please introduce yourself using \"I am...\")\n - 我是... (I am...)\n3. 你能用这些结构编一个对话吗? (Can you make up a conversation with these structures?)\n - 能,我能编一个对话。 (Yes, I can make up a conversation.)\n4. 你能说出这些字母的名字吗? (Can you say the names of these letters?)\n - 能,我能说出来。 (Yes, I can say them.)\n5. 你能把大写字母和小写字母配对吗? (Can you match the uppercase letters with the lowercase letters?)\n - 能,我能配对。 (Yes, I can match them.)\n\n### {teaching_language} Questions with {language} Answers\n1. Can you understand these names?\n - Yes, I can understand.\n2. Please introduce yourself using \"I am...\"\n - I am...\n3. Can you make up a conversation with these structures?\n - Yes, I can make up a conversation.\n4. Can you say the names of these letters?\n - Yes, I can say them.\n5. Can you match the uppercase letters with the lowercase letters?\n - Yes, I can match them.\n\n[TEACHING_PLAN_END]",
+ "The given text repeatedly describes Lily as a girl. It emphasizes that Lily is a girl multiple times. The content consistently refers to Lily as a girl.\nTranslate the above summary into a English title of less than 5 words.": "\"Emphasizing Lily's Gender\"",
+ "\n## context\n\n### Project Name\n20240110212347\n\n### Original Requirements\n['需要一个基于LLM做总结的搜索引擎']\n\n### Search Information\n-\n\n\n-----\n\n## format example\n[CONTENT]\n{\n \"Language\": \"en_us\",\n \"Programming Language\": \"Python\",\n \"Original Requirements\": \"Create a 2048 game\",\n \"Product Goals\": [\n \"Create an engaging user experience\",\n \"Improve accessibility, be responsive\",\n \"More beautiful UI\"\n ],\n \"User Stories\": [\n \"As a player, I want to be able to choose difficulty levels\",\n \"As a player, I want to see my score after each game\",\n \"As a player, I want to get restart button when I lose\",\n \"As a player, I want to see beautiful UI that make me feel good\",\n \"As a player, I want to play game via mobile phone\"\n ],\n \"Competitive Analysis\": [\n \"2048 Game A: Simple interface, lacks responsive features\",\n \"play2048.co: Beautiful and responsive UI with my best score shown\",\n \"2048game.com: Responsive UI with my best score shown, but many ads\"\n ],\n \"Competitive Quadrant Chart\": \"quadrantChart\\n title \\\"Reach and engagement of campaigns\\\"\\n x-axis \\\"Low Reach\\\" --> \\\"High Reach\\\"\\n y-axis \\\"Low Engagement\\\" --> \\\"High Engagement\\\"\\n quadrant-1 \\\"We should expand\\\"\\n quadrant-2 \\\"Need to promote\\\"\\n quadrant-3 \\\"Re-evaluate\\\"\\n quadrant-4 \\\"May be improved\\\"\\n \\\"Campaign A\\\": [0.3, 0.6]\\n \\\"Campaign B\\\": [0.45, 0.23]\\n \\\"Campaign C\\\": [0.57, 0.69]\\n \\\"Campaign D\\\": [0.78, 0.34]\\n \\\"Campaign E\\\": [0.40, 0.34]\\n \\\"Campaign F\\\": [0.35, 0.78]\\n \\\"Our Target Product\\\": [0.5, 0.6]\",\n \"Requirement Analysis\": \"\",\n \"Requirement Pool\": [\n [\n \"P0\",\n \"The main code ...\"\n ],\n [\n \"P0\",\n \"The game algorithm ...\"\n ]\n ],\n \"UI Design draft\": \"Basic function description with a simple style and layout.\",\n \"Anything UNCLEAR\": \"\"\n}\n[/CONTENT]\n\n## nodes: \": # \"\n- Language: # Provide the language used in the project, typically matching the user's requirement language.\n- Programming Language: # Python/JavaScript or other mainstream programming language.\n- Original Requirements: # Place the original user's requirements here.\n- Product Goals: typing.List[str] # Provide up to three clear, orthogonal product goals.\n- User Stories: typing.List[str] # Provide up to 3 to 5 scenario-based user stories.\n- Competitive Analysis: typing.List[str] # Provide 5 to 7 competitive products.\n- Competitive Quadrant Chart: # Use mermaid quadrantChart syntax. Distribute scores evenly between 0 and 1\n- Requirement Analysis: # Provide a detailed analysis of the requirements.\n- Requirement Pool: typing.List[typing.List[str]] # List down the top-5 requirements with their priority (P0, P1, P2).\n- UI Design draft: # Provide a simple description of UI elements, functions, style, and layout.\n- Anything UNCLEAR: # Mention any aspects of the project that are unclear and try to clarify them.\n\n\n## constraint\nLanguage: Please use the same language as Human INPUT.\nFormat: output wrapped inside [CONTENT][/CONTENT] like format example, nothing else.\n\n## action\nFollow instructions of nodes, generate output and make sure it follows the format example.\n": "[CONTENT]\n{\n \"Language\": \"zh_cn\",\n \"Programming Language\": \"LLM\",\n \"Original Requirements\": \"需要一个基于LLM做总结的搜索引擎\",\n \"Product Goals\": [],\n \"User Stories\": [],\n \"Competitive Analysis\": [],\n \"Competitive Quadrant Chart\": \"\",\n \"Requirement Analysis\": \"\",\n \"Requirement Pool\": [],\n \"UI Design draft\": \"\",\n \"Anything UNCLEAR\": \"\"\n}\n[/CONTENT]",
+ "\n## context\n\n### Project Name\n20240101\n\n### Original Requirements\n['Make a cli snake game']\n\n### Search Information\n-\n\n\n-----\n\n## format example\n[CONTENT]\n{\n \"Language\": \"en_us\",\n \"Programming Language\": \"Python\",\n \"Original Requirements\": \"Create a 2048 game\",\n \"Product Goals\": [\n \"Create an engaging user experience\",\n \"Improve accessibility, be responsive\",\n \"More beautiful UI\"\n ],\n \"User Stories\": [\n \"As a player, I want to be able to choose difficulty levels\",\n \"As a player, I want to see my score after each game\",\n \"As a player, I want to get restart button when I lose\",\n \"As a player, I want to see beautiful UI that make me feel good\",\n \"As a player, I want to play game via mobile phone\"\n ],\n \"Competitive Analysis\": [\n \"2048 Game A: Simple interface, lacks responsive features\",\n \"play2048.co: Beautiful and responsive UI with my best score shown\",\n \"2048game.com: Responsive UI with my best score shown, but many ads\"\n ],\n \"Competitive Quadrant Chart\": \"quadrantChart\\n title \\\"Reach and engagement of campaigns\\\"\\n x-axis \\\"Low Reach\\\" --> \\\"High Reach\\\"\\n y-axis \\\"Low Engagement\\\" --> \\\"High Engagement\\\"\\n quadrant-1 \\\"We should expand\\\"\\n quadrant-2 \\\"Need to promote\\\"\\n quadrant-3 \\\"Re-evaluate\\\"\\n quadrant-4 \\\"May be improved\\\"\\n \\\"Campaign A\\\": [0.3, 0.6]\\n \\\"Campaign B\\\": [0.45, 0.23]\\n \\\"Campaign C\\\": [0.57, 0.69]\\n \\\"Campaign D\\\": [0.78, 0.34]\\n \\\"Campaign E\\\": [0.40, 0.34]\\n \\\"Campaign F\\\": [0.35, 0.78]\\n \\\"Our Target Product\\\": [0.5, 0.6]\",\n \"Requirement Analysis\": \"\",\n \"Requirement Pool\": [\n [\n \"P0\",\n \"The main code ...\"\n ],\n [\n \"P0\",\n \"The game algorithm ...\"\n ]\n ],\n \"UI Design draft\": \"Basic function description with a simple style and layout.\",\n \"Anything UNCLEAR\": \"\"\n}\n[/CONTENT]\n\n## nodes: \": # \"\n- Language: # Provide the language used in the project, typically matching the user's requirement language.\n- Programming Language: # Python/JavaScript or other mainstream programming language.\n- Original Requirements: # Place the original user's requirements here.\n- Product Goals: typing.List[str] # Provide up to three clear, orthogonal product goals.\n- User Stories: typing.List[str] # Provide up to 3 to 5 scenario-based user stories.\n- Competitive Analysis: typing.List[str] # Provide 5 to 7 competitive products.\n- Competitive Quadrant Chart: # Use mermaid quadrantChart syntax. Distribute scores evenly between 0 and 1\n- Requirement Analysis: # Provide a detailed analysis of the requirements.\n- Requirement Pool: typing.List[typing.List[str]] # List down the top-5 requirements with their priority (P0, P1, P2).\n- UI Design draft: # Provide a simple description of UI elements, functions, style, and layout.\n- Anything UNCLEAR: # Mention any aspects of the project that are unclear and try to clarify them.\n\n\n## constraint\nLanguage: Please use the same language as Human INPUT.\nFormat: output wrapped inside [CONTENT][/CONTENT] like format example, nothing else.\n\n## action\nFollow instructions of nodes, generate output and make sure it follows the format example.\n": "[CONTENT]\n{\n \"Language\": \"en_us\",\n \"Programming Language\": \"Python\",\n \"Original Requirements\": \"Make a cli snake game\",\n \"Product Goals\": [],\n \"User Stories\": [],\n \"Competitive Analysis\": [],\n \"Competitive Quadrant Chart\": \"\",\n \"Requirement Analysis\": \"\",\n \"Requirement Pool\": [],\n \"UI Design draft\": \"\",\n \"Anything UNCLEAR\": \"Please provide more details on the product goals and user stories.\"\n}\n[/CONTENT]",
+ "\n## context\n{\"Language\":\"en_us\",\"Programming Language\":\"Python\",\"Original Requirements\":\"Make a cli snake game\",\"Product Goals\":[],\"User Stories\":[],\"Competitive Analysis\":[],\"Competitive Quadrant Chart\":\"\",\"Requirement Analysis\":\"\",\"Requirement Pool\":[],\"UI Design draft\":\"\",\"Anything UNCLEAR\":\"Please provide more details on the product goals and user stories.\"}\n\n-----\n\n## format example\n[CONTENT]\n{\n \"Implementation approach\": \"We will ...\",\n \"File list\": [\n \"main.py\",\n \"game.py\"\n ],\n \"Data structures and interfaces\": \"\\nclassDiagram\\n class Main {\\n -SearchEngine search_engine\\n +main() str\\n }\\n class SearchEngine {\\n -Index index\\n -Ranking ranking\\n -Summary summary\\n +search(query: str) str\\n }\\n class Index {\\n -KnowledgeBase knowledge_base\\n +create_index(data: dict)\\n +query_index(query: str) list\\n }\\n class Ranking {\\n +rank_results(results: list) list\\n }\\n class Summary {\\n +summarize_results(results: list) str\\n }\\n class KnowledgeBase {\\n +update(data: dict)\\n +fetch_data(query: str) dict\\n }\\n Main --> SearchEngine\\n SearchEngine --> Index\\n SearchEngine --> Ranking\\n SearchEngine --> Summary\\n Index --> KnowledgeBase\\n\",\n \"Program call flow\": \"\\nsequenceDiagram\\n participant M as Main\\n participant SE as SearchEngine\\n participant I as Index\\n participant R as Ranking\\n participant S as Summary\\n participant KB as KnowledgeBase\\n M->>SE: search(query)\\n SE->>I: query_index(query)\\n I->>KB: fetch_data(query)\\n KB-->>I: return data\\n I-->>SE: return results\\n SE->>R: rank_results(results)\\n R-->>SE: return ranked_results\\n SE->>S: summarize_results(ranked_results)\\n S-->>SE: return summary\\n SE-->>M: return summary\\n\",\n \"Anything UNCLEAR\": \"Clarification needed on third-party API integration, ...\"\n}\n[/CONTENT]\n\n## nodes: \": # \"\n- Implementation approach: # Analyze the difficult points of the requirements, select the appropriate open-source framework\n- File list: typing.List[str] # Only need relative paths. ALWAYS write a main.py or app.py here\n- Data structures and interfaces: # Use mermaid classDiagram code syntax, including classes, method(__init__ etc.) and functions with type annotations, CLEARLY MARK the RELATIONSHIPS between classes, and comply with PEP8 standards. The data structures SHOULD BE VERY DETAILED and the API should be comprehensive with a complete design.\n- Program call flow: # Use sequenceDiagram code syntax, COMPLETE and VERY DETAILED, using CLASSES AND API DEFINED ABOVE accurately, covering the CRUD AND INIT of each object, SYNTAX MUST BE CORRECT.\n- Anything UNCLEAR: # Mention unclear project aspects, then try to clarify it.\n\n\n## constraint\nLanguage: Please use the same language as Human INPUT.\nFormat: output wrapped inside [CONTENT][/CONTENT] like format example, nothing else.\n\n## action\nFollow instructions of nodes, generate output and make sure it follows the format example.\n": "[CONTENT]\n{\n \"Product Goals\": [\n \"Create a command-line interface (CLI) snake game\",\n \"Implement game logic for movement, collision, and scoring\",\n \"Provide a user-friendly and interactive gaming experience\"\n ],\n \"User Stories\": [\n \"As a player, I want to be able to control the snake's movement using arrow keys\",\n \"As a player, I want the game to end when the snake collides with the walls or itself\",\n \"As a player, I want to see my score displayed on the screen during and after the game\"\n ],\n \"Anything UNCLEAR\": \"Please provide more details on the specific features and functionalities expected in the snake game.\"\n}\n[/CONTENT]",
+ "\n## context\n{\"Implementation approach\":\"We will use Python and the curses library to create the snake game. The game logic will be implemented in a separate module, and the main.py file will handle the user interface and game loop.\",\"File list\":[\"main.py\",\"game.py\"],\"Data structures and interfaces\":\"\\nclassDiagram\\n class Game {\\n -Snake snake\\n -Food food\\n -Score score\\n +__init__(width: int, height: int)\\n +start_game()\\n +move_snake(direction: str)\\n +generate_food()\\n +update_score(points: int)\\n }\\n class Snake {\\n -body list\\n -direction str\\n +__init__(x: int, y: int)\\n +move(direction: str)\\n +grow()\\n +collides_with_self() bool\\n }\\n class Food {\\n -position tuple\\n +__init__(x: int, y: int)\\n +get_position() tuple\\n }\\n class Score {\\n -points int\\n +__init__()\\n +increase(points: int)\\n }\\n Game --> Snake\\n Game --> Food\\n Game --> Score\\n\",\"Program call flow\":\"\\nsequenceDiagram\\n participant M as Main\\n participant G as Game\\n M->>G: start_game()\\n M->>G: move_snake(direction)\\n G->>G: generate_food()\\n G->>G: update_score(points)\\n\",\"Anything UNCLEAR\":\"Please provide more details on the game mechanics and user interactions.\"}\n\n-----\n\n## format example\n[CONTENT]\n{\n \"Required Python packages\": [\n \"flask==1.1.2\",\n \"bcrypt==3.2.0\"\n ],\n \"Required Other language third-party packages\": [\n \"No third-party dependencies required\"\n ],\n \"Logic Analysis\": [\n [\n \"game.py\",\n \"Contains Game class and ... functions\"\n ],\n [\n \"main.py\",\n \"Contains main function, from game import Game\"\n ]\n ],\n \"Task list\": [\n \"game.py\",\n \"main.py\"\n ],\n \"Full API spec\": \"openapi: 3.0.0 ...\",\n \"Shared Knowledge\": \"'game.py' contains functions shared across the project.\",\n \"Anything UNCLEAR\": \"Clarification needed on how to start and initialize third-party libraries.\"\n}\n[/CONTENT]\n\n## nodes: \": # \"\n- Required Python packages: typing.List[str] # Provide required Python packages in requirements.txt format.\n- Required Other language third-party packages: typing.List[str] # List down the required packages for languages other than Python.\n- Logic Analysis: typing.List[typing.List[str]] # Provide a list of files with the classes/methods/functions to be implemented, including dependency analysis and imports.\n- Task list: typing.List[str] # Break down the tasks into a list of filenames, prioritized by dependency order.\n- Full API spec: # Describe all APIs using OpenAPI 3.0 spec that may be used by both frontend and backend. If front-end and back-end communication is not required, leave it blank.\n- Shared Knowledge: # Detail any shared knowledge, like common utility functions or configuration variables.\n- Anything UNCLEAR: # Mention any unclear aspects in the project management context and try to clarify them.\n\n\n## constraint\nLanguage: Please use the same language as Human INPUT.\nFormat: output wrapped inside [CONTENT][/CONTENT] like format example, nothing else.\n\n## action\nFollow instructions of nodes, generate output and make sure it follows the format example.\n": "[CONTENT]\n{\n \"Required Python packages\": [\n \"curses==2.2.0\"\n ],\n \"Required Other language third-party packages\": [\n \"No third-party dependencies required\"\n ],\n \"Logic Analysis\": [\n [\n \"game.py\",\n \"Contains Game class and ... functions\"\n ],\n [\n \"main.py\",\n \"Contains main function, from game import Game\"\n ]\n ],\n \"Task list\": [\n \"game.py\",\n \"main.py\"\n ],\n \"Full API spec\": \"\",\n \"Shared Knowledge\": \"'game.py' contains functions shared across the project.\",\n \"Anything UNCLEAR\": \"Please provide more details on the game mechanics and user interactions.\"\n}\n[/CONTENT]",
+ "\nNOTICE\nRole: You are a professional engineer; the main goal is to write google-style, elegant, modular, easy to read and maintain code\nLanguage: Please use the same language as the user requirement, but the title and code should be still in English. For example, if the user speaks Chinese, the specific text of your answer should also be in Chinese.\nATTENTION: Use '##' to SPLIT SECTIONS, not '#'. Output format carefully referenced \"Format example\".\n\n# Context\n## Design\n{\"Implementation approach\":\"We will use Python and the curses library to create the snake game. The game logic will be implemented in a separate module, and the main.py file will handle the user interface and game loop.\",\"File list\":[\"main.py\",\"game.py\"],\"Data structures and interfaces\":\"\\nclassDiagram\\n class Game {\\n -Snake snake\\n -Food food\\n -Score score\\n +__init__(width: int, height: int)\\n +start_game()\\n +move_snake(direction: str)\\n +generate_food()\\n +update_score(points: int)\\n }\\n class Snake {\\n -body list\\n -direction str\\n +__init__(x: int, y: int)\\n +move(direction: str)\\n +grow()\\n +collides_with_self() bool\\n }\\n class Food {\\n -position tuple\\n +__init__(x: int, y: int)\\n +get_position() tuple\\n }\\n class Score {\\n -points int\\n +__init__()\\n +increase(points: int)\\n }\\n Game --> Snake\\n Game --> Food\\n Game --> Score\\n\",\"Program call flow\":\"\\nsequenceDiagram\\n participant M as Main\\n participant G as Game\\n M->>G: start_game()\\n M->>G: move_snake(direction)\\n G->>G: generate_food()\\n G->>G: update_score(points)\\n\",\"Anything UNCLEAR\":\"Please provide more details on the game mechanics and user interactions.\"}\n\n## Tasks\n{\"Required Python packages\":[\"curses==2.2.0\"],\"Required Other language third-party packages\":[\"No third-party dependencies required\"],\"Logic Analysis\":[[\"game.py\",\"Contains Game class and ... functions\"],[\"main.py\",\"Contains main function, from game import Game\"]],\"Task list\":[\"game.py\",\"main.py\"],\"Full API spec\":\"\",\"Shared Knowledge\":\"'game.py' contains functions shared across the project.\",\"Anything UNCLEAR\":\"Please provide more details on the game mechanics and user interactions.\"}\n\n## Legacy Code\n```Code\n\n```\n\n## Debug logs\n```text\n\n\n\n```\n\n## Bug Feedback logs\n```text\n\n```\n\n# Format example\n## Code: game.py\n```python\n## game.py\n...\n```\n\n# Instruction: Based on the context, follow \"Format example\", write code.\n\n## Code: game.py. Write code with triple quoto, based on the following attentions and context.\n1. Only One file: do your best to implement THIS ONLY ONE FILE.\n2. COMPLETE CODE: Your code will be part of the entire project, so please implement complete, reliable, reusable code snippets.\n3. Set default value: If there is any setting, ALWAYS SET A DEFAULT VALUE, ALWAYS USE STRONG TYPE AND EXPLICIT VARIABLE. AVOID circular import.\n4. Follow design: YOU MUST FOLLOW \"Data structures and interfaces\". DONT CHANGE ANY DESIGN. Do not use public member functions that do not exist in your design.\n5. CAREFULLY CHECK THAT YOU DONT MISS ANY NECESSARY CLASS/FUNCTION IN THIS FILE.\n6. Before using a external variable/module, make sure you import it first.\n7. Write out EVERY CODE DETAIL, DON'T LEAVE TODO.\n\n": "```python\n## game.py\n\nclass Snake:\n def __init__(self, x: int, y: int):\n self.body = [(x, y)]\n self.direction = 'right'\n\n def move(self, direction: str):\n if direction == 'up' and self.direction != 'down':\n self.direction = 'up'\n elif direction == 'down' and self.direction != 'up':\n self.direction = 'down'\n elif direction == 'left' and self.direction != 'right':\n self.direction = 'left'\n elif direction == 'right' and self.direction != 'left':\n self.direction = 'right'\n\n head = self.body[0]\n x, y = head\n if self.direction == 'up':\n new_head = (x, y - 1)\n elif self.direction == 'down':\n new_head = (x, y + 1)\n elif self.direction == 'left':\n new_head = (x - 1, y)\n elif self.direction == 'right':\n new_head = (x + 1, y)\n self.body.insert(0, new_head)\n\n def grow(self):\n tail = self.body[-1]\n x, y = tail\n if self.direction == 'up':\n new_tail = (x, y + 1)\n elif self.direction == 'down':\n new_tail = (x, y - 1)\n elif self.direction == 'left':\n new_tail = (x + 1, y)\n elif self.direction == 'right':\n new_tail = (x - 1, y)\n self.body.append(new_tail)\n\n def collides_with_self(self) -> bool:\n return len(self.body) != len(set(self.body))\n\nclass Food:\n def __init__(self, x: int, y: int):\n self.position = (x, y)\n\n def get_position(self) -> tuple:\n return self.position\n\nclass Score:\n def __init__(self):\n self.points = 0\n\n def increase(self, points: int):\n self.points += points\n\nclass Game:\n def __init__(self, width: int, height: int):\n self.snake = Snake(width // 2, height // 2)\n self.food = None\n self.score = Score()\n self.width = width\n self.height = height\n\n def start_game(self):\n self.generate_food()\n\n def move_snake(self, direction: str):\n self.snake.move(direction)\n if self.check_collision():\n return False\n self.check_eat()\n return True\n\n def generate_food(self):\n import random\n x = random.randint(0, self.width - 1)\n y = random.randint(0, self.height - 1)\n self.food = Food(x, y)\n\n def update_score(self, points: int):\n self.score.increase(points)\n\n def check_collision(self) -> bool:\n head = self.snake.body[0]\n if head[0] < 0 or head[0] >= self.width or head[1] < 0 or head[1] >= self.height:\n return True\n if self.snake.collides_with_self():\n return True\n return False\n\n def check_eat(self):\n if self.snake.body[0] == self.food.get_position():\n self.snake.grow()\n self.generate_food()\n self.update_score(10)\n```",
+ "\n# System\nRole: You are a professional software engineer, and your main task is to review and revise the code. You need to ensure that the code conforms to the google-style standards, is elegantly designed and modularized, easy to read and maintain.\nLanguage: Please use the same language as the user requirement, but the title and code should be still in English. For example, if the user speaks Chinese, the specific text of your answer should also be in Chinese.\nATTENTION: Use '##' to SPLIT SECTIONS, not '#'. Output format carefully referenced \"Format example\".\n\n# Context\n## System Design\n{\"Implementation approach\":\"We will use Python and the curses library to create the snake game. The game logic will be implemented in a separate module, and the main.py file will handle the user interface and game loop.\",\"File list\":[\"main.py\",\"game.py\"],\"Data structures and interfaces\":\"\\nclassDiagram\\n class Game {\\n -Snake snake\\n -Food food\\n -Score score\\n +__init__(width: int, height: int)\\n +start_game()\\n +move_snake(direction: str)\\n +generate_food()\\n +update_score(points: int)\\n }\\n class Snake {\\n -body list\\n -direction str\\n +__init__(x: int, y: int)\\n +move(direction: str)\\n +grow()\\n +collides_with_self() bool\\n }\\n class Food {\\n -position tuple\\n +__init__(x: int, y: int)\\n +get_position() tuple\\n }\\n class Score {\\n -points int\\n +__init__()\\n +increase(points: int)\\n }\\n Game --> Snake\\n Game --> Food\\n Game --> Score\\n\",\"Program call flow\":\"\\nsequenceDiagram\\n participant M as Main\\n participant G as Game\\n M->>G: start_game()\\n M->>G: move_snake(direction)\\n G->>G: generate_food()\\n G->>G: update_score(points)\\n\",\"Anything UNCLEAR\":\"Please provide more details on the game mechanics and user interactions.\"}\n\n## Tasks\n{\"Required Python packages\":[\"curses==2.2.0\"],\"Required Other language third-party packages\":[\"No third-party dependencies required\"],\"Logic Analysis\":[[\"game.py\",\"Contains Game class and ... functions\"],[\"main.py\",\"Contains main function, from game import Game\"]],\"Task list\":[\"game.py\",\"main.py\"],\"Full API spec\":\"\",\"Shared Knowledge\":\"'game.py' contains functions shared across the project.\",\"Anything UNCLEAR\":\"Please provide more details on the game mechanics and user interactions.\"}\n\n## Code Files\n\n\n\n## Code to be Reviewed: game.py\n```Code\n## game.py\n\nclass Snake:\n def __init__(self, x: int, y: int):\n self.body = [(x, y)]\n self.direction = 'right'\n\n def move(self, direction: str):\n if direction == 'up' and self.direction != 'down':\n self.direction = 'up'\n elif direction == 'down' and self.direction != 'up':\n self.direction = 'down'\n elif direction == 'left' and self.direction != 'right':\n self.direction = 'left'\n elif direction == 'right' and self.direction != 'left':\n self.direction = 'right'\n\n head = self.body[0]\n x, y = head\n if self.direction == 'up':\n new_head = (x, y - 1)\n elif self.direction == 'down':\n new_head = (x, y + 1)\n elif self.direction == 'left':\n new_head = (x - 1, y)\n elif self.direction == 'right':\n new_head = (x + 1, y)\n self.body.insert(0, new_head)\n\n def grow(self):\n tail = self.body[-1]\n x, y = tail\n if self.direction == 'up':\n new_tail = (x, y + 1)\n elif self.direction == 'down':\n new_tail = (x, y - 1)\n elif self.direction == 'left':\n new_tail = (x + 1, y)\n elif self.direction == 'right':\n new_tail = (x - 1, y)\n self.body.append(new_tail)\n\n def collides_with_self(self) -> bool:\n return len(self.body) != len(set(self.body))\n\nclass Food:\n def __init__(self, x: int, y: int):\n self.position = (x, y)\n\n def get_position(self) -> tuple:\n return self.position\n\nclass Score:\n def __init__(self):\n self.points = 0\n\n def increase(self, points: int):\n self.points += points\n\nclass Game:\n def __init__(self, width: int, height: int):\n self.snake = Snake(width // 2, height // 2)\n self.food = None\n self.score = Score()\n self.width = width\n self.height = height\n\n def start_game(self):\n self.generate_food()\n\n def move_snake(self, direction: str):\n self.snake.move(direction)\n if self.check_collision():\n return False\n self.check_eat()\n return True\n\n def generate_food(self):\n import random\n x = random.randint(0, self.width - 1)\n y = random.randint(0, self.height - 1)\n self.food = Food(x, y)\n\n def update_score(self, points: int):\n self.score.increase(points)\n\n def check_collision(self) -> bool:\n head = self.snake.body[0]\n if head[0] < 0 or head[0] >= self.width or head[1] < 0 or head[1] >= self.height:\n return True\n if self.snake.collides_with_self():\n return True\n return False\n\n def check_eat(self):\n if self.snake.body[0] == self.food.get_position():\n self.snake.grow()\n self.generate_food()\n self.update_score(10)\n\n```\n\n\n\n# Format example 1\n## Code Review: game.py\n1. No, we should fix the logic of class A due to ...\n2. ...\n3. ...\n4. No, function B is not implemented, ...\n5. ...\n6. ...\n\n## Actions\n1. Fix the `handle_events` method to update the game state only if a move is successful.\n ```python\n def handle_events(self):\n for event in pygame.event.get():\n if event.type == pygame.QUIT:\n return False\n if event.type == pygame.KEYDOWN:\n moved = False\n if event.key == pygame.K_UP:\n moved = self.game.move('UP')\n elif event.key == pygame.K_DOWN:\n moved = self.game.move('DOWN')\n elif event.key == pygame.K_LEFT:\n moved = self.game.move('LEFT')\n elif event.key == pygame.K_RIGHT:\n moved = self.game.move('RIGHT')\n if moved:\n # Update the game state only if a move was successful\n self.render()\n return True\n ```\n2. Implement function B\n\n## Code Review Result\nLBTM\n\n# Format example 2\n## Code Review: game.py\n1. Yes.\n2. Yes.\n3. Yes.\n4. Yes.\n5. Yes.\n6. Yes.\n\n## Actions\npass\n\n## Code Review Result\nLGTM\n\n\n\n# Instruction: Based on the actual code situation, follow one of the \"Format example\". Return only 1 file under review.\n\n## Code Review: Ordered List. Based on the \"Code to be Reviewed\", provide key, clear, concise, and specific answer. If any answer is no, explain how to fix it step by step.\n1. Is the code implemented as per the requirements? If not, how to achieve it? Analyse it step by step.\n2. Is the code logic completely correct? If there are errors, please indicate how to correct them.\n3. Does the existing code follow the \"Data structures and interfaces\"?\n4. Are all functions implemented? If there is no implementation, please indicate how to achieve it step by step.\n5. Have all necessary pre-dependencies been imported? If not, indicate which ones need to be imported\n6. Are methods from other files being reused correctly?\n\n## Actions: Ordered List. Things that should be done after CR, such as implementing class A and function B\n\n## Code Review Result: str. If the code doesn't have bugs, we don't need to rewrite it, so answer LGTM and stop. ONLY ANSWER LGTM/LBTM.\nLGTM/LBTM\n\n": "## Code Review: game.py\n1. Yes, the code is implemented as per the requirements. The Snake, Food, and Score classes are correctly implemented, and the Game class handles the game logic as specified.\n2. Yes, the code logic is correct. The movement, growth, collision detection, and scoring mechanisms are implemented accurately.\n3. Yes, the existing code follows the \"Data structures and interfaces\" as specified in the class diagram.\n4. Yes, all functions are implemented as required.\n5. No, the necessary pre-dependencies have not been imported. The `curses` package needs to be imported at the beginning of the file using `import curses`.\n6. Yes, methods from other files are being reused correctly.\n\n## Actions\n1. Import the necessary pre-dependency `curses` at the beginning of the file using `import curses`.\n\n## Code Review Result\nLBTM",
+ "\n# System\nRole: You are a professional software engineer, and your main task is to review and revise the code. You need to ensure that the code conforms to the google-style standards, is elegantly designed and modularized, easy to read and maintain.\nLanguage: Please use the same language as the user requirement, but the title and code should be still in English. For example, if the user speaks Chinese, the specific text of your answer should also be in Chinese.\nATTENTION: Use '##' to SPLIT SECTIONS, not '#'. Output format carefully referenced \"Format example\".\n\n# Context\n## System Design\n{\"Implementation approach\":\"We will use Python and the curses library to create the snake game. The game logic will be implemented in a separate module, and the main.py file will handle the user interface and game loop.\",\"File list\":[\"main.py\",\"game.py\"],\"Data structures and interfaces\":\"\\nclassDiagram\\n class Game {\\n -Snake snake\\n -Food food\\n -Score score\\n +__init__(width: int, height: int)\\n +start_game()\\n +move_snake(direction: str)\\n +generate_food()\\n +update_score(points: int)\\n }\\n class Snake {\\n -body list\\n -direction str\\n +__init__(x: int, y: int)\\n +move(direction: str)\\n +grow()\\n +collides_with_self() bool\\n }\\n class Food {\\n -position tuple\\n +__init__(x: int, y: int)\\n +get_position() tuple\\n }\\n class Score {\\n -points int\\n +__init__()\\n +increase(points: int)\\n }\\n Game --> Snake\\n Game --> Food\\n Game --> Score\\n\",\"Program call flow\":\"\\nsequenceDiagram\\n participant M as Main\\n participant G as Game\\n M->>G: start_game()\\n M->>G: move_snake(direction)\\n G->>G: generate_food()\\n G->>G: update_score(points)\\n\",\"Anything UNCLEAR\":\"Please provide more details on the game mechanics and user interactions.\"}\n\n## Tasks\n{\"Required Python packages\":[\"curses==2.2.0\"],\"Required Other language third-party packages\":[\"No third-party dependencies required\"],\"Logic Analysis\":[[\"game.py\",\"Contains Game class and ... functions\"],[\"main.py\",\"Contains main function, from game import Game\"]],\"Task list\":[\"game.py\",\"main.py\"],\"Full API spec\":\"\",\"Shared Knowledge\":\"'game.py' contains functions shared across the project.\",\"Anything UNCLEAR\":\"Please provide more details on the game mechanics and user interactions.\"}\n\n## Code Files\n\n\n\n## Code to be Reviewed: game.py\n```Code\n## game.py\n\nclass Snake:\n def __init__(self, x: int, y: int):\n self.body = [(x, y)]\n self.direction = 'right'\n\n def move(self, direction: str):\n if direction == 'up' and self.direction != 'down':\n self.direction = 'up'\n elif direction == 'down' and self.direction != 'up':\n self.direction = 'down'\n elif direction == 'left' and self.direction != 'right':\n self.direction = 'left'\n elif direction == 'right' and self.direction != 'left':\n self.direction = 'right'\n\n head = self.body[0]\n x, y = head\n if self.direction == 'up':\n new_head = (x, y - 1)\n elif self.direction == 'down':\n new_head = (x, y + 1)\n elif self.direction == 'left':\n new_head = (x - 1, y)\n elif self.direction == 'right':\n new_head = (x + 1, y)\n self.body.insert(0, new_head)\n\n def grow(self):\n tail = self.body[-1]\n x, y = tail\n if self.direction == 'up':\n new_tail = (x, y + 1)\n elif self.direction == 'down':\n new_tail = (x, y - 1)\n elif self.direction == 'left':\n new_tail = (x + 1, y)\n elif self.direction == 'right':\n new_tail = (x - 1, y)\n self.body.append(new_tail)\n\n def collides_with_self(self) -> bool:\n return len(self.body) != len(set(self.body))\n\nclass Food:\n def __init__(self, x: int, y: int):\n self.position = (x, y)\n\n def get_position(self) -> tuple:\n return self.position\n\nclass Score:\n def __init__(self):\n self.points = 0\n\n def increase(self, points: int):\n self.points += points\n\nclass Game:\n def __init__(self, width: int, height: int):\n self.snake = Snake(width // 2, height // 2)\n self.food = None\n self.score = Score()\n self.width = width\n self.height = height\n\n def start_game(self):\n self.generate_food()\n\n def move_snake(self, direction: str):\n self.snake.move(direction)\n if self.check_collision():\n return False\n self.check_eat()\n return True\n\n def generate_food(self):\n import random\n x = random.randint(0, self.width - 1)\n y = random.randint(0, self.height - 1)\n self.food = Food(x, y)\n\n def update_score(self, points: int):\n self.score.increase(points)\n\n def check_collision(self) -> bool:\n head = self.snake.body[0]\n if head[0] < 0 or head[0] >= self.width or head[1] < 0 or head[1] >= self.height:\n return True\n if self.snake.collides_with_self():\n return True\n return False\n\n def check_eat(self):\n if self.snake.body[0] == self.food.get_position():\n self.snake.grow()\n self.generate_food()\n self.update_score(10)\n\n```\n\n## Code Review: game.py\n1. Yes, the code is implemented as per the requirements. The Snake, Food, and Score classes are correctly implemented, and the Game class handles the game logic as specified.\n2. Yes, the code logic is correct. The movement, growth, collision detection, and scoring mechanisms are implemented accurately.\n3. Yes, the existing code follows the \"Data structures and interfaces\" as specified in the class diagram.\n4. Yes, all functions are implemented as required.\n5. No, the necessary pre-dependencies have not been imported. The `curses` package needs to be imported at the beginning of the file using `import curses`.\n6. Yes, methods from other files are being reused correctly.\n\n## Actions\n1. Import the necessary pre-dependency `curses` at the beginning of the file using `import curses`.\n\n## Code Review Result\nLBTM\n\n# Instruction: rewrite code based on the Code Review and Actions\n## Rewrite Code: CodeBlock. If it still has some bugs, rewrite game.py with triple quotes. Do your utmost to optimize THIS SINGLE FILE. Return all completed codes and prohibit the return of unfinished codes.\n```Code\n## game.py\n...\n```\n": "```Code\n## game.py\n\nimport curses\n\nclass Snake:\n def __init__(self, x: int, y: int):\n self.body = [(x, y)]\n self.direction = 'right'\n\n def move(self, direction: str):\n if direction == 'up' and self.direction != 'down':\n self.direction = 'up'\n elif direction == 'down' and self.direction != 'up':\n self.direction = 'down'\n elif direction == 'left' and self.direction != 'right':\n self.direction = 'left'\n elif direction == 'right' and self.direction != 'left':\n self.direction = 'right'\n\n head = self.body[0]\n x, y = head\n if self.direction == 'up':\n new_head = (x, y - 1)\n elif self.direction == 'down':\n new_head = (x, y + 1)\n elif self.direction == 'left':\n new_head = (x - 1, y)\n elif self.direction == 'right':\n new_head = (x + 1, y)\n self.body.insert(0, new_head)\n\n def grow(self):\n tail = self.body[-1]\n x, y = tail\n if self.direction == 'up':\n new_tail = (x, y + 1)\n elif self.direction == 'down':\n new_tail = (x, y - 1)\n elif self.direction == 'left':\n new_tail = (x + 1, y)\n elif self.direction == 'right':\n new_tail = (x - 1, y)\n self.body.append(new_tail)\n\n def collides_with_self(self) -> bool:\n return len(self.body) != len(set(self.body))\n\nclass Food:\n def __init__(self, x: int, y: int):\n self.position = (x, y)\n\n def get_position(self) -> tuple:\n return self.position\n\nclass Score:\n def __init__(self):\n self.points = 0\n\n def increase(self, points: int):\n self.points += points\n\nclass Game:\n def __init__(self, width: int, height: int):\n self.snake = Snake(width // 2, height // 2)\n self.food = None\n self.score = Score()\n self.width = width\n self.height = height\n\n def start_game(self):\n self.generate_food()\n\n def move_snake(self, direction: str):\n self.snake.move(direction)\n if self.check_collision():\n return False\n self.check_eat()\n return True\n\n def generate_food(self):\n import random\n x = random.randint(0, self.width - 1)\n y = random.randint(0, self.height - 1)\n self.food = Food(x, y)\n\n def update_score(self, points: int):\n self.score.increase(points)\n\n def check_collision(self) -> bool:\n head = self.snake.body[0]\n if head[0] < 0 or head[0] >= self.width or head[1] < 0 or head[1] >= self.height:\n return True\n if self.snake.collides_with_self():\n return True\n return False\n\n def check_eat(self):\n if self.snake.body[0] == self.food.get_position():\n self.snake.grow()\n self.generate_food()\n self.update_score(10)\n\n```",
+ "\n# System\nRole: You are a professional software engineer, and your main task is to review and revise the code. You need to ensure that the code conforms to the google-style standards, is elegantly designed and modularized, easy to read and maintain.\nLanguage: Please use the same language as the user requirement, but the title and code should be still in English. For example, if the user speaks Chinese, the specific text of your answer should also be in Chinese.\nATTENTION: Use '##' to SPLIT SECTIONS, not '#'. Output format carefully referenced \"Format example\".\n\n# Context\n## System Design\n{\"Implementation approach\":\"We will use Python and the curses library to create the snake game. The game logic will be implemented in a separate module, and the main.py file will handle the user interface and game loop.\",\"File list\":[\"main.py\",\"game.py\"],\"Data structures and interfaces\":\"\\nclassDiagram\\n class Game {\\n -Snake snake\\n -Food food\\n -Score score\\n +__init__(width: int, height: int)\\n +start_game()\\n +move_snake(direction: str)\\n +generate_food()\\n +update_score(points: int)\\n }\\n class Snake {\\n -body list\\n -direction str\\n +__init__(x: int, y: int)\\n +move(direction: str)\\n +grow()\\n +collides_with_self() bool\\n }\\n class Food {\\n -position tuple\\n +__init__(x: int, y: int)\\n +get_position() tuple\\n }\\n class Score {\\n -points int\\n +__init__()\\n +increase(points: int)\\n }\\n Game --> Snake\\n Game --> Food\\n Game --> Score\\n\",\"Program call flow\":\"\\nsequenceDiagram\\n participant M as Main\\n participant G as Game\\n M->>G: start_game()\\n M->>G: move_snake(direction)\\n G->>G: generate_food()\\n G->>G: update_score(points)\\n\",\"Anything UNCLEAR\":\"Please provide more details on the game mechanics and user interactions.\"}\n\n## Tasks\n{\"Required Python packages\":[\"curses==2.2.0\"],\"Required Other language third-party packages\":[\"No third-party dependencies required\"],\"Logic Analysis\":[[\"game.py\",\"Contains Game class and ... functions\"],[\"main.py\",\"Contains main function, from game import Game\"]],\"Task list\":[\"game.py\",\"main.py\"],\"Full API spec\":\"\",\"Shared Knowledge\":\"'game.py' contains functions shared across the project.\",\"Anything UNCLEAR\":\"Please provide more details on the game mechanics and user interactions.\"}\n\n## Code Files\n\n\n\n## Code to be Reviewed: game.py\n```Code\n## game.py\n\nimport curses\n\nclass Snake:\n def __init__(self, x: int, y: int):\n self.body = [(x, y)]\n self.direction = 'right'\n\n def move(self, direction: str):\n if direction == 'up' and self.direction != 'down':\n self.direction = 'up'\n elif direction == 'down' and self.direction != 'up':\n self.direction = 'down'\n elif direction == 'left' and self.direction != 'right':\n self.direction = 'left'\n elif direction == 'right' and self.direction != 'left':\n self.direction = 'right'\n\n head = self.body[0]\n x, y = head\n if self.direction == 'up':\n new_head = (x, y - 1)\n elif self.direction == 'down':\n new_head = (x, y + 1)\n elif self.direction == 'left':\n new_head = (x - 1, y)\n elif self.direction == 'right':\n new_head = (x + 1, y)\n self.body.insert(0, new_head)\n\n def grow(self):\n tail = self.body[-1]\n x, y = tail\n if self.direction == 'up':\n new_tail = (x, y + 1)\n elif self.direction == 'down':\n new_tail = (x, y - 1)\n elif self.direction == 'left':\n new_tail = (x + 1, y)\n elif self.direction == 'right':\n new_tail = (x - 1, y)\n self.body.append(new_tail)\n\n def collides_with_self(self) -> bool:\n return len(self.body) != len(set(self.body))\n\nclass Food:\n def __init__(self, x: int, y: int):\n self.position = (x, y)\n\n def get_position(self) -> tuple:\n return self.position\n\nclass Score:\n def __init__(self):\n self.points = 0\n\n def increase(self, points: int):\n self.points += points\n\nclass Game:\n def __init__(self, width: int, height: int):\n self.snake = Snake(width // 2, height // 2)\n self.food = None\n self.score = Score()\n self.width = width\n self.height = height\n\n def start_game(self):\n self.generate_food()\n\n def move_snake(self, direction: str):\n self.snake.move(direction)\n if self.check_collision():\n return False\n self.check_eat()\n return True\n\n def generate_food(self):\n import random\n x = random.randint(0, self.width - 1)\n y = random.randint(0, self.height - 1)\n self.food = Food(x, y)\n\n def update_score(self, points: int):\n self.score.increase(points)\n\n def check_collision(self) -> bool:\n head = self.snake.body[0]\n if head[0] < 0 or head[0] >= self.width or head[1] < 0 or head[1] >= self.height:\n return True\n if self.snake.collides_with_self():\n return True\n return False\n\n def check_eat(self):\n if self.snake.body[0] == self.food.get_position():\n self.snake.grow()\n self.generate_food()\n self.update_score(10)\n\n\n```\n\n\n\n# Format example 1\n## Code Review: game.py\n1. No, we should fix the logic of class A due to ...\n2. ...\n3. ...\n4. No, function B is not implemented, ...\n5. ...\n6. ...\n\n## Actions\n1. Fix the `handle_events` method to update the game state only if a move is successful.\n ```python\n def handle_events(self):\n for event in pygame.event.get():\n if event.type == pygame.QUIT:\n return False\n if event.type == pygame.KEYDOWN:\n moved = False\n if event.key == pygame.K_UP:\n moved = self.game.move('UP')\n elif event.key == pygame.K_DOWN:\n moved = self.game.move('DOWN')\n elif event.key == pygame.K_LEFT:\n moved = self.game.move('LEFT')\n elif event.key == pygame.K_RIGHT:\n moved = self.game.move('RIGHT')\n if moved:\n # Update the game state only if a move was successful\n self.render()\n return True\n ```\n2. Implement function B\n\n## Code Review Result\nLBTM\n\n# Format example 2\n## Code Review: game.py\n1. Yes.\n2. Yes.\n3. Yes.\n4. Yes.\n5. Yes.\n6. Yes.\n\n## Actions\npass\n\n## Code Review Result\nLGTM\n\n\n\n# Instruction: Based on the actual code situation, follow one of the \"Format example\". Return only 1 file under review.\n\n## Code Review: Ordered List. Based on the \"Code to be Reviewed\", provide key, clear, concise, and specific answer. If any answer is no, explain how to fix it step by step.\n1. Is the code implemented as per the requirements? If not, how to achieve it? Analyse it step by step.\n2. Is the code logic completely correct? If there are errors, please indicate how to correct them.\n3. Does the existing code follow the \"Data structures and interfaces\"?\n4. Are all functions implemented? If there is no implementation, please indicate how to achieve it step by step.\n5. Have all necessary pre-dependencies been imported? If not, indicate which ones need to be imported\n6. Are methods from other files being reused correctly?\n\n## Actions: Ordered List. Things that should be done after CR, such as implementing class A and function B\n\n## Code Review Result: str. If the code doesn't have bugs, we don't need to rewrite it, so answer LGTM and stop. ONLY ANSWER LGTM/LBTM.\nLGTM/LBTM\n\n": "## Code Review: game.py\n1. Yes, the code is implemented as per the requirements.\n2. Yes, the code logic is completely correct.\n3. Yes, the existing code follows the \"Data structures and interfaces\".\n4. Yes, all functions are implemented.\n5. Yes, all necessary pre-dependencies have been imported.\n6. Yes, methods from other files are being reused correctly.\n\n## Code Review Result\nLGTM",
+ "\nNOTICE\nRole: You are a professional engineer; the main goal is to write google-style, elegant, modular, easy to read and maintain code\nLanguage: Please use the same language as the user requirement, but the title and code should be still in English. For example, if the user speaks Chinese, the specific text of your answer should also be in Chinese.\nATTENTION: Use '##' to SPLIT SECTIONS, not '#'. Output format carefully referenced \"Format example\".\n\n# Context\n## Design\n{\"Implementation approach\":\"We will use Python and the curses library to create the snake game. The game logic will be implemented in a separate module, and the main.py file will handle the user interface and game loop.\",\"File list\":[\"main.py\",\"game.py\"],\"Data structures and interfaces\":\"\\nclassDiagram\\n class Game {\\n -Snake snake\\n -Food food\\n -Score score\\n +__init__(width: int, height: int)\\n +start_game()\\n +move_snake(direction: str)\\n +generate_food()\\n +update_score(points: int)\\n }\\n class Snake {\\n -body list\\n -direction str\\n +__init__(x: int, y: int)\\n +move(direction: str)\\n +grow()\\n +collides_with_self() bool\\n }\\n class Food {\\n -position tuple\\n +__init__(x: int, y: int)\\n +get_position() tuple\\n }\\n class Score {\\n -points int\\n +__init__()\\n +increase(points: int)\\n }\\n Game --> Snake\\n Game --> Food\\n Game --> Score\\n\",\"Program call flow\":\"\\nsequenceDiagram\\n participant M as Main\\n participant G as Game\\n M->>G: start_game()\\n M->>G: move_snake(direction)\\n G->>G: generate_food()\\n G->>G: update_score(points)\\n\",\"Anything UNCLEAR\":\"Please provide more details on the game mechanics and user interactions.\"}\n\n## Tasks\n{\"Required Python packages\":[\"curses==2.2.0\"],\"Required Other language third-party packages\":[\"No third-party dependencies required\"],\"Logic Analysis\":[[\"game.py\",\"Contains Game class and ... functions\"],[\"main.py\",\"Contains main function, from game import Game\"]],\"Task list\":[\"game.py\",\"main.py\"],\"Full API spec\":\"\",\"Shared Knowledge\":\"'game.py' contains functions shared across the project.\",\"Anything UNCLEAR\":\"Please provide more details on the game mechanics and user interactions.\"}\n\n## Legacy Code\n```Code\n----- game.py\n## game.py\n\nimport curses\n\nclass Snake:\n def __init__(self, x: int, y: int):\n self.body = [(x, y)]\n self.direction = 'right'\n\n def move(self, direction: str):\n if direction == 'up' and self.direction != 'down':\n self.direction = 'up'\n elif direction == 'down' and self.direction != 'up':\n self.direction = 'down'\n elif direction == 'left' and self.direction != 'right':\n self.direction = 'left'\n elif direction == 'right' and self.direction != 'left':\n self.direction = 'right'\n\n head = self.body[0]\n x, y = head\n if self.direction == 'up':\n new_head = (x, y - 1)\n elif self.direction == 'down':\n new_head = (x, y + 1)\n elif self.direction == 'left':\n new_head = (x - 1, y)\n elif self.direction == 'right':\n new_head = (x + 1, y)\n self.body.insert(0, new_head)\n\n def grow(self):\n tail = self.body[-1]\n x, y = tail\n if self.direction == 'up':\n new_tail = (x, y + 1)\n elif self.direction == 'down':\n new_tail = (x, y - 1)\n elif self.direction == 'left':\n new_tail = (x + 1, y)\n elif self.direction == 'right':\n new_tail = (x - 1, y)\n self.body.append(new_tail)\n\n def collides_with_self(self) -> bool:\n return len(self.body) != len(set(self.body))\n\nclass Food:\n def __init__(self, x: int, y: int):\n self.position = (x, y)\n\n def get_position(self) -> tuple:\n return self.position\n\nclass Score:\n def __init__(self):\n self.points = 0\n\n def increase(self, points: int):\n self.points += points\n\nclass Game:\n def __init__(self, width: int, height: int):\n self.snake = Snake(width // 2, height // 2)\n self.food = None\n self.score = Score()\n self.width = width\n self.height = height\n\n def start_game(self):\n self.generate_food()\n\n def move_snake(self, direction: str):\n self.snake.move(direction)\n if self.check_collision():\n return False\n self.check_eat()\n return True\n\n def generate_food(self):\n import random\n x = random.randint(0, self.width - 1)\n y = random.randint(0, self.height - 1)\n self.food = Food(x, y)\n\n def update_score(self, points: int):\n self.score.increase(points)\n\n def check_collision(self) -> bool:\n head = self.snake.body[0]\n if head[0] < 0 or head[0] >= self.width or head[1] < 0 or head[1] >= self.height:\n return True\n if self.snake.collides_with_self():\n return True\n return False\n\n def check_eat(self):\n if self.snake.body[0] == self.food.get_position():\n self.snake.grow()\n self.generate_food()\n self.update_score(10)\n\n\n```\n\n## Debug logs\n```text\n\n\n\n```\n\n## Bug Feedback logs\n```text\n\n```\n\n# Format example\n## Code: main.py\n```python\n## main.py\n...\n```\n\n# Instruction: Based on the context, follow \"Format example\", write code.\n\n## Code: main.py. Write code with triple quoto, based on the following attentions and context.\n1. Only One file: do your best to implement THIS ONLY ONE FILE.\n2. COMPLETE CODE: Your code will be part of the entire project, so please implement complete, reliable, reusable code snippets.\n3. Set default value: If there is any setting, ALWAYS SET A DEFAULT VALUE, ALWAYS USE STRONG TYPE AND EXPLICIT VARIABLE. AVOID circular import.\n4. Follow design: YOU MUST FOLLOW \"Data structures and interfaces\". DONT CHANGE ANY DESIGN. Do not use public member functions that do not exist in your design.\n5. CAREFULLY CHECK THAT YOU DONT MISS ANY NECESSARY CLASS/FUNCTION IN THIS FILE.\n6. Before using a external variable/module, make sure you import it first.\n7. Write out EVERY CODE DETAIL, DON'T LEAVE TODO.\n\n": "```python\n## main.py\n\nfrom game import Game\n\ndef main():\n width = 20 # Set default width\n height = 20 # Set default height\n game = Game(width, height)\n game.start_game()\n\n while True:\n # Get user input for direction\n direction = input(\"Enter direction (up/down/left/right): \")\n if direction in ['up', 'down', 'left', 'right']:\n if not game.move_snake(direction):\n print(\"Game over!\")\n break\n else:\n print(\"Invalid direction!\")\n\nif __name__ == \"__main__\":\n main()\n```",
+ "\n# System\nRole: You are a professional software engineer, and your main task is to review and revise the code. You need to ensure that the code conforms to the google-style standards, is elegantly designed and modularized, easy to read and maintain.\nLanguage: Please use the same language as the user requirement, but the title and code should be still in English. For example, if the user speaks Chinese, the specific text of your answer should also be in Chinese.\nATTENTION: Use '##' to SPLIT SECTIONS, not '#'. Output format carefully referenced \"Format example\".\n\n# Context\n## System Design\n{\"Implementation approach\":\"We will use Python and the curses library to create the snake game. The game logic will be implemented in a separate module, and the main.py file will handle the user interface and game loop.\",\"File list\":[\"main.py\",\"game.py\"],\"Data structures and interfaces\":\"\\nclassDiagram\\n class Game {\\n -Snake snake\\n -Food food\\n -Score score\\n +__init__(width: int, height: int)\\n +start_game()\\n +move_snake(direction: str)\\n +generate_food()\\n +update_score(points: int)\\n }\\n class Snake {\\n -body list\\n -direction str\\n +__init__(x: int, y: int)\\n +move(direction: str)\\n +grow()\\n +collides_with_self() bool\\n }\\n class Food {\\n -position tuple\\n +__init__(x: int, y: int)\\n +get_position() tuple\\n }\\n class Score {\\n -points int\\n +__init__()\\n +increase(points: int)\\n }\\n Game --> Snake\\n Game --> Food\\n Game --> Score\\n\",\"Program call flow\":\"\\nsequenceDiagram\\n participant M as Main\\n participant G as Game\\n M->>G: start_game()\\n M->>G: move_snake(direction)\\n G->>G: generate_food()\\n G->>G: update_score(points)\\n\",\"Anything UNCLEAR\":\"Please provide more details on the game mechanics and user interactions.\"}\n\n## Tasks\n{\"Required Python packages\":[\"curses==2.2.0\"],\"Required Other language third-party packages\":[\"No third-party dependencies required\"],\"Logic Analysis\":[[\"game.py\",\"Contains Game class and ... functions\"],[\"main.py\",\"Contains main function, from game import Game\"]],\"Task list\":[\"game.py\",\"main.py\"],\"Full API spec\":\"\",\"Shared Knowledge\":\"'game.py' contains functions shared across the project.\",\"Anything UNCLEAR\":\"Please provide more details on the game mechanics and user interactions.\"}\n\n## Code Files\n----- game.py\n## game.py\n\nimport curses\n\nclass Snake:\n def __init__(self, x: int, y: int):\n self.body = [(x, y)]\n self.direction = 'right'\n\n def move(self, direction: str):\n if direction == 'up' and self.direction != 'down':\n self.direction = 'up'\n elif direction == 'down' and self.direction != 'up':\n self.direction = 'down'\n elif direction == 'left' and self.direction != 'right':\n self.direction = 'left'\n elif direction == 'right' and self.direction != 'left':\n self.direction = 'right'\n\n head = self.body[0]\n x, y = head\n if self.direction == 'up':\n new_head = (x, y - 1)\n elif self.direction == 'down':\n new_head = (x, y + 1)\n elif self.direction == 'left':\n new_head = (x - 1, y)\n elif self.direction == 'right':\n new_head = (x + 1, y)\n self.body.insert(0, new_head)\n\n def grow(self):\n tail = self.body[-1]\n x, y = tail\n if self.direction == 'up':\n new_tail = (x, y + 1)\n elif self.direction == 'down':\n new_tail = (x, y - 1)\n elif self.direction == 'left':\n new_tail = (x + 1, y)\n elif self.direction == 'right':\n new_tail = (x - 1, y)\n self.body.append(new_tail)\n\n def collides_with_self(self) -> bool:\n return len(self.body) != len(set(self.body))\n\nclass Food:\n def __init__(self, x: int, y: int):\n self.position = (x, y)\n\n def get_position(self) -> tuple:\n return self.position\n\nclass Score:\n def __init__(self):\n self.points = 0\n\n def increase(self, points: int):\n self.points += points\n\nclass Game:\n def __init__(self, width: int, height: int):\n self.snake = Snake(width // 2, height // 2)\n self.food = None\n self.score = Score()\n self.width = width\n self.height = height\n\n def start_game(self):\n self.generate_food()\n\n def move_snake(self, direction: str):\n self.snake.move(direction)\n if self.check_collision():\n return False\n self.check_eat()\n return True\n\n def generate_food(self):\n import random\n x = random.randint(0, self.width - 1)\n y = random.randint(0, self.height - 1)\n self.food = Food(x, y)\n\n def update_score(self, points: int):\n self.score.increase(points)\n\n def check_collision(self) -> bool:\n head = self.snake.body[0]\n if head[0] < 0 or head[0] >= self.width or head[1] < 0 or head[1] >= self.height:\n return True\n if self.snake.collides_with_self():\n return True\n return False\n\n def check_eat(self):\n if self.snake.body[0] == self.food.get_position():\n self.snake.grow()\n self.generate_food()\n self.update_score(10)\n\n\n\n\n## Code to be Reviewed: main.py\n```Code\n## main.py\n\nfrom game import Game\n\ndef main():\n width = 20 # Set default width\n height = 20 # Set default height\n game = Game(width, height)\n game.start_game()\n\n while True:\n # Get user input for direction\n direction = input(\"Enter direction (up/down/left/right): \")\n if direction in ['up', 'down', 'left', 'right']:\n if not game.move_snake(direction):\n print(\"Game over!\")\n break\n else:\n print(\"Invalid direction!\")\n\nif __name__ == \"__main__\":\n main()\n\n```\n\n\n\n# Format example 1\n## Code Review: main.py\n1. No, we should fix the logic of class A due to ...\n2. ...\n3. ...\n4. No, function B is not implemented, ...\n5. ...\n6. ...\n\n## Actions\n1. Fix the `handle_events` method to update the game state only if a move is successful.\n ```python\n def handle_events(self):\n for event in pygame.event.get():\n if event.type == pygame.QUIT:\n return False\n if event.type == pygame.KEYDOWN:\n moved = False\n if event.key == pygame.K_UP:\n moved = self.game.move('UP')\n elif event.key == pygame.K_DOWN:\n moved = self.game.move('DOWN')\n elif event.key == pygame.K_LEFT:\n moved = self.game.move('LEFT')\n elif event.key == pygame.K_RIGHT:\n moved = self.game.move('RIGHT')\n if moved:\n # Update the game state only if a move was successful\n self.render()\n return True\n ```\n2. Implement function B\n\n## Code Review Result\nLBTM\n\n# Format example 2\n## Code Review: main.py\n1. Yes.\n2. Yes.\n3. Yes.\n4. Yes.\n5. Yes.\n6. Yes.\n\n## Actions\npass\n\n## Code Review Result\nLGTM\n\n\n\n# Instruction: Based on the actual code situation, follow one of the \"Format example\". Return only 1 file under review.\n\n## Code Review: Ordered List. Based on the \"Code to be Reviewed\", provide key, clear, concise, and specific answer. If any answer is no, explain how to fix it step by step.\n1. Is the code implemented as per the requirements? If not, how to achieve it? Analyse it step by step.\n2. Is the code logic completely correct? If there are errors, please indicate how to correct them.\n3. Does the existing code follow the \"Data structures and interfaces\"?\n4. Are all functions implemented? If there is no implementation, please indicate how to achieve it step by step.\n5. Have all necessary pre-dependencies been imported? If not, indicate which ones need to be imported\n6. Are methods from other files being reused correctly?\n\n## Actions: Ordered List. Things that should be done after CR, such as implementing class A and function B\n\n## Code Review Result: str. If the code doesn't have bugs, we don't need to rewrite it, so answer LGTM and stop. ONLY ANSWER LGTM/LBTM.\nLGTM/LBTM\n\n": "## Code Review: main.py\n1. The code is implemented as per the requirements. The main function initializes the game and handles user input for the snake's direction.\n2. The code logic is correct. It correctly handles user input and updates the game state based on the snake's movement.\n3. The existing code follows the \"Data structures and interfaces\" as defined in the class diagram.\n4. All necessary functions are implemented in the code.\n5. The necessary pre-dependencies have been imported. The required Python package \"curses==2.2.0\" has been imported.\n6. The methods from the \"game.py\" file are being reused correctly.\n\n## Actions\npass\n\n## Code Review Result\nLGTM",
+ "\n## context\n\n### Project Name\n20240110212717\n\n### Original Requirements\n['开发一个基于大语言模型与私有知识库的搜索引擎,希望可以基于大语言模型进行搜索总结']\n\n### Search Information\n-\n\n\n-----\n\n## format example\n[CONTENT]\n{\n \"Language\": \"en_us\",\n \"Programming Language\": \"Python\",\n \"Original Requirements\": \"Create a 2048 game\",\n \"Product Goals\": [\n \"Create an engaging user experience\",\n \"Improve accessibility, be responsive\",\n \"More beautiful UI\"\n ],\n \"User Stories\": [\n \"As a player, I want to be able to choose difficulty levels\",\n \"As a player, I want to see my score after each game\",\n \"As a player, I want to get restart button when I lose\",\n \"As a player, I want to see beautiful UI that make me feel good\",\n \"As a player, I want to play game via mobile phone\"\n ],\n \"Competitive Analysis\": [\n \"2048 Game A: Simple interface, lacks responsive features\",\n \"play2048.co: Beautiful and responsive UI with my best score shown\",\n \"2048game.com: Responsive UI with my best score shown, but many ads\"\n ],\n \"Competitive Quadrant Chart\": \"quadrantChart\\n title \\\"Reach and engagement of campaigns\\\"\\n x-axis \\\"Low Reach\\\" --> \\\"High Reach\\\"\\n y-axis \\\"Low Engagement\\\" --> \\\"High Engagement\\\"\\n quadrant-1 \\\"We should expand\\\"\\n quadrant-2 \\\"Need to promote\\\"\\n quadrant-3 \\\"Re-evaluate\\\"\\n quadrant-4 \\\"May be improved\\\"\\n \\\"Campaign A\\\": [0.3, 0.6]\\n \\\"Campaign B\\\": [0.45, 0.23]\\n \\\"Campaign C\\\": [0.57, 0.69]\\n \\\"Campaign D\\\": [0.78, 0.34]\\n \\\"Campaign E\\\": [0.40, 0.34]\\n \\\"Campaign F\\\": [0.35, 0.78]\\n \\\"Our Target Product\\\": [0.5, 0.6]\",\n \"Requirement Analysis\": \"\",\n \"Requirement Pool\": [\n [\n \"P0\",\n \"The main code ...\"\n ],\n [\n \"P0\",\n \"The game algorithm ...\"\n ]\n ],\n \"UI Design draft\": \"Basic function description with a simple style and layout.\",\n \"Anything UNCLEAR\": \"\"\n}\n[/CONTENT]\n\n## nodes: \": # \"\n- Language: # Provide the language used in the project, typically matching the user's requirement language.\n- Programming Language: # Python/JavaScript or other mainstream programming language.\n- Original Requirements: # Place the original user's requirements here.\n- Product Goals: typing.List[str] # Provide up to three clear, orthogonal product goals.\n- User Stories: typing.List[str] # Provide up to 3 to 5 scenario-based user stories.\n- Competitive Analysis: typing.List[str] # Provide 5 to 7 competitive products.\n- Competitive Quadrant Chart: # Use mermaid quadrantChart syntax. Distribute scores evenly between 0 and 1\n- Requirement Analysis: # Provide a detailed analysis of the requirements.\n- Requirement Pool: typing.List[typing.List[str]] # List down the top-5 requirements with their priority (P0, P1, P2).\n- UI Design draft: # Provide a simple description of UI elements, functions, style, and layout.\n- Anything UNCLEAR: # Mention any aspects of the project that are unclear and try to clarify them.\n\n\n## constraint\nLanguage: Please use the same language as Human INPUT.\nFormat: output wrapped inside [CONTENT][/CONTENT] like format example, nothing else.\n\n## action\nFollow instructions of nodes, generate output and make sure it follows the format example.\n": "[CONTENT]\n{\n \"Language\": \"zh_cn\",\n \"Programming Language\": \"Python\",\n \"Original Requirements\": \"开发一个基于大语言模型与私有知识库的搜索引擎,希望可以基于大语言模型进行搜索总结\",\n \"Product Goals\": [],\n \"User Stories\": [],\n \"Competitive Analysis\": [],\n \"Competitive Quadrant Chart\": \"\",\n \"Requirement Analysis\": \"\",\n \"Requirement Pool\": [],\n \"UI Design draft\": \"\",\n \"Anything UNCLEAR\": \"\"\n}\n[/CONTENT]",
+ "\n## context\n\n### Project Name\n20240110212717\n\n### Original Requirements\n['']\n\n### Search Information\n-\n\n\n-----\n\n## format example\n[CONTENT]\n{\n \"Language\": \"en_us\",\n \"Programming Language\": \"Python\",\n \"Original Requirements\": \"Create a 2048 game\",\n \"Product Goals\": [\n \"Create an engaging user experience\",\n \"Improve accessibility, be responsive\",\n \"More beautiful UI\"\n ],\n \"User Stories\": [\n \"As a player, I want to be able to choose difficulty levels\",\n \"As a player, I want to see my score after each game\",\n \"As a player, I want to get restart button when I lose\",\n \"As a player, I want to see beautiful UI that make me feel good\",\n \"As a player, I want to play game via mobile phone\"\n ],\n \"Competitive Analysis\": [\n \"2048 Game A: Simple interface, lacks responsive features\",\n \"play2048.co: Beautiful and responsive UI with my best score shown\",\n \"2048game.com: Responsive UI with my best score shown, but many ads\"\n ],\n \"Competitive Quadrant Chart\": \"quadrantChart\\n title \\\"Reach and engagement of campaigns\\\"\\n x-axis \\\"Low Reach\\\" --> \\\"High Reach\\\"\\n y-axis \\\"Low Engagement\\\" --> \\\"High Engagement\\\"\\n quadrant-1 \\\"We should expand\\\"\\n quadrant-2 \\\"Need to promote\\\"\\n quadrant-3 \\\"Re-evaluate\\\"\\n quadrant-4 \\\"May be improved\\\"\\n \\\"Campaign A\\\": [0.3, 0.6]\\n \\\"Campaign B\\\": [0.45, 0.23]\\n \\\"Campaign C\\\": [0.57, 0.69]\\n \\\"Campaign D\\\": [0.78, 0.34]\\n \\\"Campaign E\\\": [0.40, 0.34]\\n \\\"Campaign F\\\": [0.35, 0.78]\\n \\\"Our Target Product\\\": [0.5, 0.6]\",\n \"Requirement Analysis\": \"\",\n \"Requirement Pool\": [\n [\n \"P0\",\n \"The main code ...\"\n ],\n [\n \"P0\",\n \"The game algorithm ...\"\n ]\n ],\n \"UI Design draft\": \"Basic function description with a simple style and layout.\",\n \"Anything UNCLEAR\": \"\"\n}\n[/CONTENT]\n\n## nodes: \": # \"\n- Language: # Provide the language used in the project, typically matching the user's requirement language.\n- Programming Language: # Python/JavaScript or other mainstream programming language.\n- Original Requirements: # Place the original user's requirements here.\n- Product Goals: typing.List[str] # Provide up to three clear, orthogonal product goals.\n- User Stories: typing.List[str] # Provide up to 3 to 5 scenario-based user stories.\n- Competitive Analysis: typing.List[str] # Provide 5 to 7 competitive products.\n- Competitive Quadrant Chart: # Use mermaid quadrantChart syntax. Distribute scores evenly between 0 and 1\n- Requirement Analysis: # Provide a detailed analysis of the requirements.\n- Requirement Pool: typing.List[typing.List[str]] # List down the top-5 requirements with their priority (P0, P1, P2).\n- UI Design draft: # Provide a simple description of UI elements, functions, style, and layout.\n- Anything UNCLEAR: # Mention any aspects of the project that are unclear and try to clarify them.\n\n\n## constraint\nLanguage: Please use the same language as Human INPUT.\nFormat: output wrapped inside [CONTENT][/CONTENT] like format example, nothing else.\n\n## action\nFollow instructions of nodes, generate output and make sure it follows the format example.\n": "[CONTENT]\n{\n \"Language\": \"en_us\",\n \"Programming Language\": \"Python\",\n \"Original Requirements\": \"\",\n \"Product Goals\": [],\n \"User Stories\": [],\n \"Competitive Analysis\": [],\n \"Competitive Quadrant Chart\": \"\",\n \"Requirement Analysis\": \"\",\n \"Requirement Pool\": [],\n \"UI Design draft\": \"\",\n \"Anything UNCLEAR\": \"\"\n}\n[/CONTENT]",
+ "\n## context\n\n### Project Name\n20240110220803\n\n### Original Requirements\n['需要一个基于LLM做总结的搜索引擎']\n\n### Search Information\n-\n\n\n-----\n\n## format example\n[CONTENT]\n{\n \"Language\": \"en_us\",\n \"Programming Language\": \"Python\",\n \"Original Requirements\": \"Create a 2048 game\",\n \"Product Goals\": [\n \"Create an engaging user experience\",\n \"Improve accessibility, be responsive\",\n \"More beautiful UI\"\n ],\n \"User Stories\": [\n \"As a player, I want to be able to choose difficulty levels\",\n \"As a player, I want to see my score after each game\",\n \"As a player, I want to get restart button when I lose\",\n \"As a player, I want to see beautiful UI that make me feel good\",\n \"As a player, I want to play game via mobile phone\"\n ],\n \"Competitive Analysis\": [\n \"2048 Game A: Simple interface, lacks responsive features\",\n \"play2048.co: Beautiful and responsive UI with my best score shown\",\n \"2048game.com: Responsive UI with my best score shown, but many ads\"\n ],\n \"Competitive Quadrant Chart\": \"quadrantChart\\n title \\\"Reach and engagement of campaigns\\\"\\n x-axis \\\"Low Reach\\\" --> \\\"High Reach\\\"\\n y-axis \\\"Low Engagement\\\" --> \\\"High Engagement\\\"\\n quadrant-1 \\\"We should expand\\\"\\n quadrant-2 \\\"Need to promote\\\"\\n quadrant-3 \\\"Re-evaluate\\\"\\n quadrant-4 \\\"May be improved\\\"\\n \\\"Campaign A\\\": [0.3, 0.6]\\n \\\"Campaign B\\\": [0.45, 0.23]\\n \\\"Campaign C\\\": [0.57, 0.69]\\n \\\"Campaign D\\\": [0.78, 0.34]\\n \\\"Campaign E\\\": [0.40, 0.34]\\n \\\"Campaign F\\\": [0.35, 0.78]\\n \\\"Our Target Product\\\": [0.5, 0.6]\",\n \"Requirement Analysis\": \"\",\n \"Requirement Pool\": [\n [\n \"P0\",\n \"The main code ...\"\n ],\n [\n \"P0\",\n \"The game algorithm ...\"\n ]\n ],\n \"UI Design draft\": \"Basic function description with a simple style and layout.\",\n \"Anything UNCLEAR\": \"\"\n}\n[/CONTENT]\n\n## nodes: \": # \"\n- Language: # Provide the language used in the project, typically matching the user's requirement language.\n- Programming Language: # Python/JavaScript or other mainstream programming language.\n- Original Requirements: # Place the original user's requirements here.\n- Product Goals: typing.List[str] # Provide up to three clear, orthogonal product goals.\n- User Stories: typing.List[str] # Provide up to 3 to 5 scenario-based user stories.\n- Competitive Analysis: typing.List[str] # Provide 5 to 7 competitive products.\n- Competitive Quadrant Chart: # Use mermaid quadrantChart syntax. Distribute scores evenly between 0 and 1\n- Requirement Analysis: # Provide a detailed analysis of the requirements.\n- Requirement Pool: typing.List[typing.List[str]] # List down the top-5 requirements with their priority (P0, P1, P2).\n- UI Design draft: # Provide a simple description of UI elements, functions, style, and layout.\n- Anything UNCLEAR: # Mention any aspects of the project that are unclear and try to clarify them.\n\n\n## constraint\nLanguage: Please use the same language as Human INPUT.\nFormat: output wrapped inside [CONTENT][/CONTENT] like format example, nothing else.\n\n## action\nFollow instructions of nodes, generate output and make sure it follows the format example.\n": "[CONTENT]\n{\n \"Language\": \"zh_cn\",\n \"Programming Language\": \"LLM\",\n \"Original Requirements\": \"需要一个基于LLM做总结的搜索引擎\",\n \"Product Goals\": [],\n \"User Stories\": [],\n \"Competitive Analysis\": [],\n \"Competitive Quadrant Chart\": \"\",\n \"Requirement Analysis\": \"\",\n \"Requirement Pool\": [],\n \"UI Design draft\": \"\",\n \"Anything UNCLEAR\": \"\"\n}\n[/CONTENT]",
+ "You are a python code to Mermaid Sequence Diagram translator in function detail#SYSTEM_MSG_END#```python\n#!/usr/bin/env python\n# -*- coding: utf-8 -*-\nimport asyncio\nimport shutil\nfrom pathlib import Path\n\nimport typer\n\nfrom metagpt.config2 import config\nfrom metagpt.const import METAGPT_ROOT\n\napp = typer.Typer(add_completion=False, pretty_exceptions_show_locals=False)\n\n\ndef generate_repo(\n idea,\n investment,\n n_round,\n code_review,\n run_tests,\n implement,\n project_name,\n inc,\n project_path,\n reqa_file,\n max_auto_summarize_code,\n recover_path,\n):\n \"\"\"Run the startup logic. Can be called from CLI or other Python scripts.\"\"\"\n from metagpt.roles import (\n Architect,\n Engineer,\n ProductManager,\n ProjectManager,\n QaEngineer,\n )\n from metagpt.team import Team\n\n config.update_via_cli(project_path, project_name, inc, reqa_file, max_auto_summarize_code)\n\n if not recover_path:\n company = Team()\n company.hire(\n [\n ProductManager(),\n Architect(),\n ProjectManager(),\n ]\n )\n\n if implement or code_review:\n company.hire([Engineer(n_borg=5, use_code_review=code_review)])\n\n if run_tests:\n company.hire([QaEngineer()])\n else:\n stg_path = Path(recover_path)\n if not stg_path.exists() or not str(stg_path).endswith(\"team\"):\n raise FileNotFoundError(f\"{recover_path} not exists or not endswith `team`\")\n\n company = Team.deserialize(stg_path=stg_path)\n idea = company.idea\n\n company.invest(investment)\n company.run_project(idea)\n asyncio.run(company.run(n_round=n_round))\n\n\n@app.command(\"\", help=\"Start a new project.\")\ndef startup(\n idea: str = typer.Argument(None, help=\"Your innovative idea, such as 'Create a 2048 game.'\"),\n investment: float = typer.Option(default=3.0, help=\"Dollar amount to invest in the AI company.\"),\n n_round: int = typer.Option(default=5, help=\"Number of rounds for the simulation.\"),\n code_review: bool = typer.Option(default=True, help=\"Whether to use code review.\"),\n run_tests: bool = typer.Option(default=False, help=\"Whether to enable QA for adding & running tests.\"),\n implement: bool = typer.Option(default=True, help=\"Enable or disable code implementation.\"),\n project_name: str = typer.Option(default=\"\", help=\"Unique project name, such as 'game_2048'.\"),\n inc: bool = typer.Option(default=False, help=\"Incremental mode. Use it to coop with existing repo.\"),\n project_path: str = typer.Option(\n default=\"\",\n help=\"Specify the directory path of the old version project to fulfill the incremental requirements.\",\n ),\n reqa_file: str = typer.Option(\n default=\"\", help=\"Specify the source file name for rewriting the quality assurance code.\"\n ),\n max_auto_summarize_code: int = typer.Option(\n default=0,\n help=\"The maximum number of times the 'SummarizeCode' action is automatically invoked, with -1 indicating \"\n \"unlimited. This parameter is used for debugging the workflow.\",\n ),\n recover_path: str = typer.Option(default=None, help=\"recover the project from existing serialized storage\"),\n init_config: bool = typer.Option(default=False, help=\"Initialize the configuration file for MetaGPT.\"),\n):\n \"\"\"Run a startup. Be a boss.\"\"\"\n if init_config:\n copy_config_to()\n return\n\n if idea is None:\n typer.echo(\"Missing argument 'IDEA'. Run 'metagpt --help' for more information.\")\n raise typer.Exit()\n\n return generate_repo(\n idea,\n investment,\n n_round,\n code_review,\n run_tests,\n implement,\n project_name,\n inc,\n project_path,\n reqa_file,\n max_auto_summarize_code,\n recover_path,\n )\n\n\ndef copy_config_to(config_path=METAGPT_ROOT / \"config\" / \"config2.yaml\"):\n \"\"\"Initialize the configuration file for MetaGPT.\"\"\"\n target_path = Path.home() / \".metagpt\" / \"config2.yaml\"\n\n # 创建目标目录(如果不存在)\n target_path.parent.mkdir(parents=True, exist_ok=True)\n\n # 如果目标文件已经存在,则重命名为 .bak\n if target_path.exists():\n backup_path = target_path.with_suffix(\".bak\")\n target_path.rename(backup_path)\n print(f\"Existing configuration file backed up at {backup_path}\")\n\n # 复制文件\n shutil.copy(str(config_path), target_path)\n print(f\"Configuration file initialized at {target_path}\")\n\n\nif __name__ == \"__main__\":\n app()\n\n```\n\n---\nTranslate the code above into Mermaid Sequence Diagram.": "```mermaid\nsequenceDiagram\n participant app as app\n participant typer as typer\n participant generate_repo as generate_repo\n participant Team as company\n participant ProductManager as ProductManager\n participant Architect as Architect\n participant ProjectManager as ProjectManager\n participant Engineer as Engineer\n participant QaEngineer as QaEngineer\n\n app -> typer: startup()\n typer -> generate_repo: generate_repo()\n generate_repo -> config: config.update_via_cli()\n generate_repo -> company: company.hire([ProductManager, Architect, ProjectManager])\n generate_repo -> company: company.hire([Engineer])\n generate_repo -> company: company.hire([QaEngineer])\n generate_repo -> company: company.invest()\n generate_repo -> company: company.run_project()\n generate_repo -> company: asyncio.run(company.run())\n\n Note right of generate_repo: If recover_path is provided,
deserialize Team from recover_path\n```",
+ "You are a python code to Mermaid Sequence Diagram translator in function detail#SYSTEM_MSG_END#```python\n#!/usr/bin/env python\n\nfrom __future__ import annotations\n\nimport asyncio\nimport json\nfrom concurrent import futures\nfrom typing import Literal, overload\n\nfrom metagpt.config2 import config\n\ntry:\n from duckduckgo_search import DDGS\nexcept ImportError:\n raise ImportError(\n \"To use this module, you should have the `duckduckgo_search` Python package installed. \"\n \"You can install it by running the command: `pip install -e.[search-ddg]`\"\n )\n\n\nclass DDGAPIWrapper:\n \"\"\"Wrapper around duckduckgo_search API.\n\n To use this module, you should have the `duckduckgo_search` Python package installed.\n \"\"\"\n\n def __init__(\n self,\n *,\n loop: asyncio.AbstractEventLoop | None = None,\n executor: futures.Executor | None = None,\n ):\n kwargs = {}\n if config.proxy:\n kwargs[\"proxies\"] = config.proxy\n self.loop = loop\n self.executor = executor\n self.ddgs = DDGS(**kwargs)\n\n @overload\n def run(\n self,\n query: str,\n max_results: int = 8,\n as_string: Literal[True] = True,\n focus: list[str] | None = None,\n ) -> str:\n ...\n\n @overload\n def run(\n self,\n query: str,\n max_results: int = 8,\n as_string: Literal[False] = False,\n focus: list[str] | None = None,\n ) -> list[dict[str, str]]:\n ...\n\n async def run(\n self,\n query: str,\n max_results: int = 8,\n as_string: bool = True,\n ) -> str | list[dict]:\n \"\"\"Return the results of a Google search using the official Google API\n\n Args:\n query: The search query.\n max_results: The number of results to return.\n as_string: A boolean flag to determine the return type of the results. If True, the function will\n return a formatted string with the search results. If False, it will return a list of dictionaries\n containing detailed information about each search result.\n\n Returns:\n The results of the search.\n \"\"\"\n loop = self.loop or asyncio.get_event_loop()\n future = loop.run_in_executor(\n self.executor,\n self._search_from_ddgs,\n query,\n max_results,\n )\n search_results = await future\n\n # Return the list of search result URLs\n if as_string:\n return json.dumps(search_results, ensure_ascii=False)\n return search_results\n\n def _search_from_ddgs(self, query: str, max_results: int):\n return [\n {\"link\": i[\"href\"], \"snippet\": i[\"body\"], \"title\": i[\"title\"]}\n for (_, i) in zip(range(max_results), self.ddgs.text(query))\n ]\n\n\nif __name__ == \"__main__\":\n import fire\n\n fire.Fire(DDGAPIWrapper().run)\n\n```\n\n---\nTranslate the code above into Mermaid Sequence Diagram.": "```mermaid\nsequenceDiagram\n participant User\n participant DDGAPIWrapper\n participant asyncio\n participant futures\n participant DDGS\n participant config\n\n User->>DDGAPIWrapper: run(query, max_results, as_string)\n DDGAPIWrapper->>asyncio: get_event_loop()\n asyncio->>DDGAPIWrapper: loop\n alt config.proxy\n DDGAPIWrapper->>config: proxy\n end\n DDGAPIWrapper->>futures: Executor\n futures->>DDGAPIWrapper: executor\n DDGAPIWrapper->>DDGS: __init__(**kwargs)\n DDGAPIWrapper->>asyncio: run_in_executor(executor, _search_from_ddgs, query, max_results)\n asyncio->>DDGAPIWrapper: future\n DDGAPIWrapper->>DDGS: text(query)\n DDGS-->>DDGAPIWrapper: search results\n DDGAPIWrapper-->>asyncio: search_results\n asyncio-->>DDGAPIWrapper: await future\n DDGAPIWrapper-->>User: search results\n```",
+ "You are a python code to Mermaid Sequence Diagram translator in function detail#SYSTEM_MSG_END#```python\n#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\"\"\"\n@Time : 2023/5/23 18:27\n@Author : alexanderwu\n@File : search_engine_serpapi.py\n\"\"\"\nfrom typing import Any, Dict, Optional, Tuple\n\nimport aiohttp\nfrom pydantic import BaseModel, ConfigDict, Field, field_validator\n\nfrom metagpt.config2 import config\n\n\nclass SerpAPIWrapper(BaseModel):\n model_config = ConfigDict(arbitrary_types_allowed=True)\n\n search_engine: Any = None #: :meta private:\n params: dict = Field(\n default_factory=lambda: {\n \"engine\": \"google\",\n \"google_domain\": \"google.com\",\n \"gl\": \"us\",\n \"hl\": \"en\",\n }\n )\n # should add `validate_default=True` to check with default value\n serpapi_api_key: Optional[str] = Field(default=None, validate_default=True)\n aiosession: Optional[aiohttp.ClientSession] = None\n\n @field_validator(\"serpapi_api_key\", mode=\"before\")\n @classmethod\n def check_serpapi_api_key(cls, val: str):\n val = val or config.search[\"serpapi\"].api_key\n if not val:\n raise ValueError(\n \"To use, make sure you provide the serpapi_api_key when constructing an object. Alternatively, \"\n \"ensure that the environment variable SERPAPI_API_KEY is set with your API key. You can obtain \"\n \"an API key from https://serpapi.com/.\"\n )\n return val\n\n async def run(self, query, max_results: int = 8, as_string: bool = True, **kwargs: Any) -> str:\n \"\"\"Run query through SerpAPI and parse result async.\"\"\"\n result = await self.results(query, max_results)\n return self._process_response(result, as_string=as_string)\n\n async def results(self, query: str, max_results: int) -> dict:\n \"\"\"Use aiohttp to run query through SerpAPI and return the results async.\"\"\"\n\n def construct_url_and_params() -> Tuple[str, Dict[str, str]]:\n params = self.get_params(query)\n params[\"source\"] = \"python\"\n params[\"num\"] = max_results\n params[\"output\"] = \"json\"\n url = \"https://serpapi.com/search\"\n return url, params\n\n url, params = construct_url_and_params()\n if not self.aiosession:\n async with aiohttp.ClientSession() as session:\n async with session.get(url, params=params) as response:\n res = await response.json()\n else:\n async with self.aiosession.get(url, params=params) as response:\n res = await response.json()\n\n return res\n\n def get_params(self, query: str) -> Dict[str, str]:\n \"\"\"Get parameters for SerpAPI.\"\"\"\n _params = {\n \"api_key\": self.serpapi_api_key,\n \"q\": query,\n }\n params = {**self.params, **_params}\n return params\n\n @staticmethod\n def _process_response(res: dict, as_string: bool) -> str:\n \"\"\"Process response from SerpAPI.\"\"\"\n # logger.debug(res)\n focus = [\"title\", \"snippet\", \"link\"]\n get_focused = lambda x: {i: j for i, j in x.items() if i in focus}\n\n if \"error\" in res.keys():\n raise ValueError(f\"Got error from SerpAPI: {res['error']}\")\n if \"answer_box\" in res.keys() and \"answer\" in res[\"answer_box\"].keys():\n toret = res[\"answer_box\"][\"answer\"]\n elif \"answer_box\" in res.keys() and \"snippet\" in res[\"answer_box\"].keys():\n toret = res[\"answer_box\"][\"snippet\"]\n elif \"answer_box\" in res.keys() and \"snippet_highlighted_words\" in res[\"answer_box\"].keys():\n toret = res[\"answer_box\"][\"snippet_highlighted_words\"][0]\n elif \"sports_results\" in res.keys() and \"game_spotlight\" in res[\"sports_results\"].keys():\n toret = res[\"sports_results\"][\"game_spotlight\"]\n elif \"knowledge_graph\" in res.keys() and \"description\" in res[\"knowledge_graph\"].keys():\n toret = res[\"knowledge_graph\"][\"description\"]\n elif \"snippet\" in res[\"organic_results\"][0].keys():\n toret = res[\"organic_results\"][0][\"snippet\"]\n else:\n toret = \"No good search result found\"\n\n toret_l = []\n if \"answer_box\" in res.keys() and \"snippet\" in res[\"answer_box\"].keys():\n toret_l += [get_focused(res[\"answer_box\"])]\n if res.get(\"organic_results\"):\n toret_l += [get_focused(i) for i in res.get(\"organic_results\")]\n\n return str(toret) + \"\\n\" + str(toret_l) if as_string else toret_l\n\n\nif __name__ == \"__main__\":\n import fire\n\n fire.Fire(SerpAPIWrapper().run)\n\n```\n\n---\nTranslate the code above into Mermaid Sequence Diagram.": "```mermaid\nsequenceDiagram\n participant SerpAPIWrapper\n participant aiohttp\n participant session\n participant response\n participant fire\n\n Note over SerpAPIWrapper: Initialization\n SerpAPIWrapper->>SerpAPIWrapper: __init__\n\n Note over SerpAPIWrapper: Run query through SerpAPI\n SerpAPIWrapper->>SerpAPIWrapper: run(query, max_results, as_string, **kwargs)\n SerpAPIWrapper->>SerpAPIWrapper: results(query, max_results)\n SerpAPIWrapper->>SerpAPIWrapper: get_params(query)\n SerpAPIWrapper->>aiohttp: session.get(url, params)\n aiohttp->>session: get(url, params)\n session->>response: response.json()\n response-->>session: res\n session-->>aiohttp: res\n aiohttp-->>SerpAPIWrapper: res\n SerpAPIWrapper-->>SerpAPIWrapper: _process_response(result, as_string)\n\n Note over SerpAPIWrapper: Use aiohttp to run query through SerpAPI\n SerpAPIWrapper->>SerpAPIWrapper: results(query, max_results)\n SerpAPIWrapper->>SerpAPIWrapper: get_params(query)\n SerpAPIWrapper->>aiohttp: ClientSession()\n aiohttp->>session: get(url, params)\n session->>response: response.json()\n response-->>session: res\n session-->>aiohttp: res\n aiohttp-->>SerpAPIWrapper: res\n\n Note over SerpAPIWrapper: Get parameters for SerpAPI\n SerpAPIWrapper->>SerpAPIWrapper: get_params(query)\n\n Note over SerpAPIWrapper: Process response from SerpAPI\n SerpAPIWrapper->>SerpAPIWrapper: _process_response(res, as_string)\n\n Note over fire: Main function\n fire->>SerpAPIWrapper: run\n\n```",
+ "You are a python code to Mermaid Sequence Diagram translator in function detail#SYSTEM_MSG_END#```python\n#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\"\"\"\n@Time : 2023/5/23 18:27\n@Author : alexanderwu\n@File : search_engine_serpapi.py\n\"\"\"\nimport json\nfrom typing import Any, Dict, Optional, Tuple\n\nimport aiohttp\nfrom pydantic import BaseModel, ConfigDict, Field, field_validator\n\nfrom metagpt.config2 import config\n\n\nclass SerperWrapper(BaseModel):\n model_config = ConfigDict(arbitrary_types_allowed=True)\n\n search_engine: Any = None #: :meta private:\n payload: dict = Field(default_factory=lambda: {\"page\": 1, \"num\": 10})\n serper_api_key: Optional[str] = Field(default=None, validate_default=True)\n aiosession: Optional[aiohttp.ClientSession] = None\n\n @field_validator(\"serper_api_key\", mode=\"before\")\n @classmethod\n def check_serper_api_key(cls, val: str):\n val = val or config.search[\"serper\"].api_key\n if not val:\n raise ValueError(\n \"To use, make sure you provide the serper_api_key when constructing an object. Alternatively, \"\n \"ensure that the environment variable SERPER_API_KEY is set with your API key. You can obtain \"\n \"an API key from https://serper.dev/.\"\n )\n return val\n\n async def run(self, query: str, max_results: int = 8, as_string: bool = True, **kwargs: Any) -> str:\n \"\"\"Run query through Serper and parse result async.\"\"\"\n if isinstance(query, str):\n return self._process_response((await self.results([query], max_results))[0], as_string=as_string)\n else:\n results = [self._process_response(res, as_string) for res in await self.results(query, max_results)]\n return \"\\n\".join(results) if as_string else results\n\n async def results(self, queries: list[str], max_results: int = 8) -> dict:\n \"\"\"Use aiohttp to run query through Serper and return the results async.\"\"\"\n\n def construct_url_and_payload_and_headers() -> Tuple[str, Dict[str, str]]:\n payloads = self.get_payloads(queries, max_results)\n url = \"https://google.serper.dev/search\"\n headers = self.get_headers()\n return url, payloads, headers\n\n url, payloads, headers = construct_url_and_payload_and_headers()\n if not self.aiosession:\n async with aiohttp.ClientSession() as session:\n async with session.post(url, data=payloads, headers=headers) as response:\n res = await response.json()\n else:\n async with self.aiosession.get.post(url, data=payloads, headers=headers) as response:\n res = await response.json()\n\n return res\n\n def get_payloads(self, queries: list[str], max_results: int) -> Dict[str, str]:\n \"\"\"Get payloads for Serper.\"\"\"\n payloads = []\n for query in queries:\n _payload = {\n \"q\": query,\n \"num\": max_results,\n }\n payloads.append({**self.payload, **_payload})\n return json.dumps(payloads, sort_keys=True)\n\n def get_headers(self) -> Dict[str, str]:\n headers = {\"X-API-KEY\": self.serper_api_key, \"Content-Type\": \"application/json\"}\n return headers\n\n @staticmethod\n def _process_response(res: dict, as_string: bool = False) -> str:\n \"\"\"Process response from SerpAPI.\"\"\"\n # logger.debug(res)\n focus = [\"title\", \"snippet\", \"link\"]\n\n def get_focused(x):\n return {i: j for i, j in x.items() if i in focus}\n\n if \"error\" in res.keys():\n raise ValueError(f\"Got error from SerpAPI: {res['error']}\")\n if \"answer_box\" in res.keys() and \"answer\" in res[\"answer_box\"].keys():\n toret = res[\"answer_box\"][\"answer\"]\n elif \"answer_box\" in res.keys() and \"snippet\" in res[\"answer_box\"].keys():\n toret = res[\"answer_box\"][\"snippet\"]\n elif \"answer_box\" in res.keys() and \"snippet_highlighted_words\" in res[\"answer_box\"].keys():\n toret = res[\"answer_box\"][\"snippet_highlighted_words\"][0]\n elif \"sports_results\" in res.keys() and \"game_spotlight\" in res[\"sports_results\"].keys():\n toret = res[\"sports_results\"][\"game_spotlight\"]\n elif \"knowledge_graph\" in res.keys() and \"description\" in res[\"knowledge_graph\"].keys():\n toret = res[\"knowledge_graph\"][\"description\"]\n elif \"snippet\" in res[\"organic\"][0].keys():\n toret = res[\"organic\"][0][\"snippet\"]\n else:\n toret = \"No good search result found\"\n\n toret_l = []\n if \"answer_box\" in res.keys() and \"snippet\" in res[\"answer_box\"].keys():\n toret_l += [get_focused(res[\"answer_box\"])]\n if res.get(\"organic\"):\n toret_l += [get_focused(i) for i in res.get(\"organic\")]\n\n return str(toret) + \"\\n\" + str(toret_l) if as_string else toret_l\n\n\nif __name__ == \"__main__\":\n import fire\n\n fire.Fire(SerperWrapper().run)\n\n```\n\n---\nTranslate the code above into Mermaid Sequence Diagram.": "```mermaid\nsequenceDiagram\n participant User\n participant SerperWrapper\n participant aiohttp\n participant pydantic\n participant config\n\n User ->> SerperWrapper: run(query: str, max_results: int, as_string: bool, **kwargs: Any)\n SerperWrapper ->> SerperWrapper: _process_response(response: dict, as_string: bool)\n SerperWrapper ->> SerperWrapper: get_payloads(queries: list[str], max_results: int)\n SerperWrapper ->> SerperWrapper: get_headers()\n SerperWrapper ->> aiohttp: ClientSession.post(url, data, headers)\n aiohttp ->> SerperWrapper: response\n SerperWrapper ->> aiohttp: ClientSession.get.post(url, data, headers)\n aiohttp ->> SerperWrapper: response\n SerperWrapper ->> aiohttp: ClientSession.post(url, data, headers)\n aiohttp ->> SerperWrapper: response\n SerperWrapper ->> User: str\n```",
+ "You are a python code to Mermaid Sequence Diagram translator in function detail#SYSTEM_MSG_END#```python\n#!/usr/bin/env python\n# -*- coding: utf-8 -*-\nfrom __future__ import annotations\n\nimport asyncio\nimport json\nfrom concurrent import futures\nfrom typing import Optional\nfrom urllib.parse import urlparse\n\nimport httplib2\nfrom pydantic import BaseModel, ConfigDict, Field, field_validator\n\nfrom metagpt.config2 import config\nfrom metagpt.logs import logger\n\ntry:\n from googleapiclient.discovery import build\n from googleapiclient.errors import HttpError\nexcept ImportError:\n raise ImportError(\n \"To use this module, you should have the `google-api-python-client` Python package installed. \"\n \"You can install it by running the command: `pip install -e.[search-google]`\"\n )\n\n\nclass GoogleAPIWrapper(BaseModel):\n model_config = ConfigDict(arbitrary_types_allowed=True)\n\n google_api_key: Optional[str] = Field(default=None, validate_default=True)\n google_cse_id: Optional[str] = Field(default=None, validate_default=True)\n loop: Optional[asyncio.AbstractEventLoop] = None\n executor: Optional[futures.Executor] = None\n\n @field_validator(\"google_api_key\", mode=\"before\")\n @classmethod\n def check_google_api_key(cls, val: str):\n val = val or config.search[\"google\"].api_key\n if not val:\n raise ValueError(\n \"To use, make sure you provide the google_api_key when constructing an object. Alternatively, \"\n \"ensure that the environment variable GOOGLE_API_KEY is set with your API key. You can obtain \"\n \"an API key from https://console.cloud.google.com/apis/credentials.\"\n )\n return val\n\n @field_validator(\"google_cse_id\", mode=\"before\")\n @classmethod\n def check_google_cse_id(cls, val: str):\n val = val or config.search[\"google\"].cse_id\n if not val:\n raise ValueError(\n \"To use, make sure you provide the google_cse_id when constructing an object. Alternatively, \"\n \"ensure that the environment variable GOOGLE_CSE_ID is set with your API key. You can obtain \"\n \"an API key from https://programmablesearchengine.google.com/controlpanel/create.\"\n )\n return val\n\n @property\n def google_api_client(self):\n build_kwargs = {\"developerKey\": self.google_api_key}\n if config.proxy:\n parse_result = urlparse(config.proxy)\n proxy_type = parse_result.scheme\n if proxy_type == \"https\":\n proxy_type = \"http\"\n build_kwargs[\"http\"] = httplib2.Http(\n proxy_info=httplib2.ProxyInfo(\n getattr(httplib2.socks, f\"PROXY_TYPE_{proxy_type.upper()}\"),\n parse_result.hostname,\n parse_result.port,\n ),\n )\n service = build(\"customsearch\", \"v1\", **build_kwargs)\n return service.cse()\n\n async def run(\n self,\n query: str,\n max_results: int = 8,\n as_string: bool = True,\n focus: list[str] | None = None,\n ) -> str | list[dict]:\n \"\"\"Return the results of a Google search using the official Google API.\n\n Args:\n query: The search query.\n max_results: The number of results to return.\n as_string: A boolean flag to determine the return type of the results. If True, the function will\n return a formatted string with the search results. If False, it will return a list of dictionaries\n containing detailed information about each search result.\n focus: Specific information to be focused on from each search result.\n\n Returns:\n The results of the search.\n \"\"\"\n loop = self.loop or asyncio.get_event_loop()\n future = loop.run_in_executor(\n self.executor, self.google_api_client.list(q=query, num=max_results, cx=self.google_cse_id).execute\n )\n try:\n result = await future\n # Extract the search result items from the response\n search_results = result.get(\"items\", [])\n\n except HttpError as e:\n # Handle errors in the API call\n logger.exception(f\"fail to search {query} for {e}\")\n search_results = []\n\n focus = focus or [\"snippet\", \"link\", \"title\"]\n details = [{i: j for i, j in item_dict.items() if i in focus} for item_dict in search_results]\n # Return the list of search result URLs\n if as_string:\n return safe_google_results(details)\n\n return details\n\n\ndef safe_google_results(results: str | list) -> str:\n \"\"\"Return the results of a google search in a safe format.\n\n Args:\n results: The search results.\n\n Returns:\n The results of the search.\n \"\"\"\n if isinstance(results, list):\n safe_message = json.dumps([result for result in results])\n else:\n safe_message = results.encode(\"utf-8\", \"ignore\").decode(\"utf-8\")\n return safe_message\n\n\nif __name__ == \"__main__\":\n import fire\n\n fire.Fire(GoogleAPIWrapper().run)\n\n```\n\n---\nTranslate the code above into Mermaid Sequence Diagram.": "```mermaid\nsequenceDiagram\n participant BaseModel\n participant ConfigDict\n participant Field\n participant field_validator\n participant asyncio\n participant futures\n participant urlparse\n participant httplib2\n participant logger\n participant build\n participant HttpError\n participant GoogleAPIWrapper\n participant fire\n\n BaseModel ->> ConfigDict: model_config\n BaseModel ->> Field: google_api_key\n BaseModel ->> Field: google_cse_id\n BaseModel ->> Field: loop\n BaseModel ->> Field: executor\n Field ->> field_validator: check_google_api_key\n Field ->> field_validator: check_google_cse_id\n GoogleAPIWrapper ->> urlparse: parse_result\n urlparse ->> httplib2: Http\n urlparse ->> httplib2: ProxyInfo\n httplib2 ->> logger: exception\n build ->> GoogleAPIWrapper: google_api_client\n GoogleAPIWrapper ->> asyncio: run\n asyncio ->> futures: run_in_executor\n futures ->> GoogleAPIWrapper: google_api_client.list\n GoogleAPIWrapper ->> HttpError: HttpError\n HttpError ->> logger: exception\n GoogleAPIWrapper ->> safe_google_results: safe_google_results\n safe_google_results -->> GoogleAPIWrapper: safe_message\n GoogleAPIWrapper -->> fire: run\n```",
+ "You are a python code to Mermaid Sequence Diagram translator in function detail#SYSTEM_MSG_END#```python\nNone\n```\n\n---\nTranslate the code above into Mermaid Sequence Diagram.": "```mermaid\nsequenceDiagram\n participant PythonCode\n PythonCode->>Mermaid: None\n```",
+ "You are a python code to Mermaid Sequence Diagram translator in function detail#SYSTEM_MSG_END#```python\n#!/usr/bin/env python\n\"\"\"\n@Modified By: mashenquan, 2023/8/22. A definition has been provided for the return value of _think: returning false indicates that further reasoning cannot continue.\n@Modified By: mashenquan, 2023-11-1. According to Chapter 2.2.1 and 2.2.2 of RFC 116, change the data type of\n the `cause_by` value in the `Message` to a string to support the new message distribution feature.\n\"\"\"\n\nimport asyncio\nimport re\n\nfrom pydantic import BaseModel\n\nfrom metagpt.actions import Action, CollectLinks, ConductResearch, WebBrowseAndSummarize\nfrom metagpt.actions.research import get_research_system_text\nfrom metagpt.const import RESEARCH_PATH\nfrom metagpt.logs import logger\nfrom metagpt.roles.role import Role, RoleReactMode\nfrom metagpt.schema import Message\n\n\nclass Report(BaseModel):\n topic: str\n links: dict[str, list[str]] = None\n summaries: list[tuple[str, str]] = None\n content: str = \"\"\n\n\nclass Researcher(Role):\n name: str = \"David\"\n profile: str = \"Researcher\"\n goal: str = \"Gather information and conduct research\"\n constraints: str = \"Ensure accuracy and relevance of information\"\n language: str = \"en-us\"\n\n def __init__(self, **kwargs):\n super().__init__(**kwargs)\n self.set_actions(\n [CollectLinks(name=self.name), WebBrowseAndSummarize(name=self.name), ConductResearch(name=self.name)]\n )\n self._set_react_mode(react_mode=RoleReactMode.BY_ORDER.value)\n if self.language not in (\"en-us\", \"zh-cn\"):\n logger.warning(f\"The language `{self.language}` has not been tested, it may not work.\")\n\n async def _think(self) -> bool:\n if self.rc.todo is None:\n self._set_state(0)\n return True\n\n if self.rc.state + 1 < len(self.states):\n self._set_state(self.rc.state + 1)\n else:\n self.set_todo(None)\n return False\n\n async def _act(self) -> Message:\n logger.info(f\"{self._setting}: to do {self.rc.todo}({self.rc.todo.name})\")\n todo = self.rc.todo\n msg = self.rc.memory.get(k=1)[0]\n if isinstance(msg.instruct_content, Report):\n instruct_content = msg.instruct_content\n topic = instruct_content.topic\n else:\n topic = msg.content\n\n research_system_text = self.research_system_text(topic, todo)\n if isinstance(todo, CollectLinks):\n links = await todo.run(topic, 4, 4)\n ret = Message(\n content=\"\", instruct_content=Report(topic=topic, links=links), role=self.profile, cause_by=todo\n )\n elif isinstance(todo, WebBrowseAndSummarize):\n links = instruct_content.links\n todos = (todo.run(*url, query=query, system_text=research_system_text) for (query, url) in links.items())\n summaries = await asyncio.gather(*todos)\n summaries = list((url, summary) for i in summaries for (url, summary) in i.items() if summary)\n ret = Message(\n content=\"\", instruct_content=Report(topic=topic, summaries=summaries), role=self.profile, cause_by=todo\n )\n else:\n summaries = instruct_content.summaries\n summary_text = \"\\n---\\n\".join(f\"url: {url}\\nsummary: {summary}\" for (url, summary) in summaries)\n content = await self.rc.todo.run(topic, summary_text, system_text=research_system_text)\n ret = Message(\n content=\"\",\n instruct_content=Report(topic=topic, content=content),\n role=self.profile,\n cause_by=self.rc.todo,\n )\n self.rc.memory.add(ret)\n return ret\n\n def research_system_text(self, topic, current_task: Action) -> str:\n \"\"\"BACKWARD compatible\n This allows sub-class able to define its own system prompt based on topic.\n return the previous implementation to have backward compatible\n Args:\n topic:\n language:\n\n Returns: str\n \"\"\"\n return get_research_system_text(topic, self.language)\n\n async def react(self) -> Message:\n msg = await super().react()\n report = msg.instruct_content\n self.write_report(report.topic, report.content)\n return msg\n\n def write_report(self, topic: str, content: str):\n filename = re.sub(r'[\\\\/:\"*?<>|]+', \" \", topic)\n filename = filename.replace(\"\\n\", \"\")\n if not RESEARCH_PATH.exists():\n RESEARCH_PATH.mkdir(parents=True)\n filepath = RESEARCH_PATH / f\"{filename}.md\"\n filepath.write_text(content)\n\n\nif __name__ == \"__main__\":\n import fire\n\n async def main(topic: str, language=\"en-us\"):\n role = Researcher(language=language)\n await role.run(topic)\n\n fire.Fire(main)\n\n```\n\n---\nTranslate the code above into Mermaid Sequence Diagram.": "```mermaid\nsequenceDiagram\n participant Role\n participant CollectLinks\n participant WebBrowseAndSummarize\n participant ConductResearch\n participant Message\n participant Report\n\n Role->>Role: Gather information and conduct research\n Role->>Role: Ensure accuracy and relevance of information\n Role->>Role: Set react mode to BY_ORDER\n\n Role->>Role: to do {todo}({todo.name})\n Role->>CollectLinks: run(topic, 4, 4)\n CollectLinks-->>Role: links\n Role->>Message: Report(topic, links)\n Role->>Role: Add message to memory\n\n Role->>WebBrowseAndSummarize: run(url, query, system_text)\n WebBrowseAndSummarize-->>Role: summaries\n Role->>Message: Report(topic, summaries)\n Role->>Role: Add message to memory\n\n Role->>ConductResearch: run(topic, summary_text, system_text)\n ConductResearch-->>Role: content\n Role->>Message: Report(topic, content)\n Role->>Role: Add message to memory\n\n Role->>Role: React\n Role->>Role: Write report to file\n```",
+ "You are a python code to Mermaid Sequence Diagram translator in function detail#SYSTEM_MSG_END#```python\n\"\"\"Code Docstring Generator.\n\nThis script provides a tool to automatically generate docstrings for Python code. It uses the specified style to create\ndocstrings for the given code and system text.\n\nUsage:\n python3 -m metagpt.actions.write_docstring [--overwrite] [--style=]\n\nArguments:\n filename The path to the Python file for which you want to generate docstrings.\n\nOptions:\n --overwrite If specified, overwrite the original file with the code containing docstrings.\n --style= Specify the style of the generated docstrings.\n Valid values: 'google', 'numpy', or 'sphinx'.\n Default: 'google'\n\nExample:\n python3 -m metagpt.actions.write_docstring ./metagpt/startup.py --overwrite False --style=numpy\n\nThis script uses the 'fire' library to create a command-line interface. It generates docstrings for the given Python code using\nthe specified docstring style and adds them to the code.\n\"\"\"\nfrom __future__ import annotations\n\nimport ast\nfrom pathlib import Path\nfrom typing import Literal, Optional\n\nfrom metagpt.actions.action import Action\nfrom metagpt.utils.common import OutputParser, aread, awrite\nfrom metagpt.utils.pycst import merge_docstring\n\nPYTHON_DOCSTRING_SYSTEM = \"\"\"### Requirements\n1. Add docstrings to the given code following the {style} style.\n2. Replace the function body with an Ellipsis object(...) to reduce output.\n3. If the types are already annotated, there is no need to include them in the docstring.\n4. Extract only class, function or the docstrings for the module parts from the given Python code, avoiding any other text.\n\n### Input Example\n```python\ndef function_with_pep484_type_annotations(param1: int) -> bool:\n return isinstance(param1, int)\n\nclass ExampleError(Exception):\n def __init__(self, msg: str):\n self.msg = msg\n```\n\n### Output Example\n```python\n{example}\n```\n\"\"\"\n\n# https://www.sphinx-doc.org/en/master/usage/extensions/napoleon.html\n\nPYTHON_DOCSTRING_EXAMPLE_GOOGLE = '''\ndef function_with_pep484_type_annotations(param1: int) -> bool:\n \"\"\"Example function with PEP 484 type annotations.\n\n Extended description of function.\n\n Args:\n param1: The first parameter.\n\n Returns:\n The return value. True for success, False otherwise.\n \"\"\"\n ...\n\nclass ExampleError(Exception):\n \"\"\"Exceptions are documented in the same way as classes.\n\n The __init__ method was documented in the class level docstring.\n\n Args:\n msg: Human readable string describing the exception.\n\n Attributes:\n msg: Human readable string describing the exception.\n \"\"\"\n ...\n'''\n\nPYTHON_DOCSTRING_EXAMPLE_NUMPY = '''\ndef function_with_pep484_type_annotations(param1: int) -> bool:\n \"\"\"\n Example function with PEP 484 type annotations.\n\n Extended description of function.\n\n Parameters\n ----------\n param1\n The first parameter.\n\n Returns\n -------\n bool\n The return value. True for success, False otherwise.\n \"\"\"\n ...\n\nclass ExampleError(Exception):\n \"\"\"\n Exceptions are documented in the same way as classes.\n\n The __init__ method was documented in the class level docstring.\n\n Parameters\n ----------\n msg\n Human readable string describing the exception.\n\n Attributes\n ----------\n msg\n Human readable string describing the exception.\n \"\"\"\n ...\n'''\n\nPYTHON_DOCSTRING_EXAMPLE_SPHINX = '''\ndef function_with_pep484_type_annotations(param1: int) -> bool:\n \"\"\"Example function with PEP 484 type annotations.\n\n Extended description of function.\n\n :param param1: The first parameter.\n :type param1: int\n\n :return: The return value. True for success, False otherwise.\n :rtype: bool\n \"\"\"\n ...\n\nclass ExampleError(Exception):\n \"\"\"Exceptions are documented in the same way as classes.\n\n The __init__ method was documented in the class level docstring.\n\n :param msg: Human-readable string describing the exception.\n :type msg: str\n \"\"\"\n ...\n'''\n\n_python_docstring_style = {\n \"google\": PYTHON_DOCSTRING_EXAMPLE_GOOGLE.strip(),\n \"numpy\": PYTHON_DOCSTRING_EXAMPLE_NUMPY.strip(),\n \"sphinx\": PYTHON_DOCSTRING_EXAMPLE_SPHINX.strip(),\n}\n\n\nclass WriteDocstring(Action):\n \"\"\"This class is used to write docstrings for code.\n\n Attributes:\n desc: A string describing the action.\n \"\"\"\n\n desc: str = \"Write docstring for code.\"\n i_context: Optional[str] = None\n\n async def run(\n self,\n code: str,\n system_text: str = PYTHON_DOCSTRING_SYSTEM,\n style: Literal[\"google\", \"numpy\", \"sphinx\"] = \"google\",\n ) -> str:\n \"\"\"Writes docstrings for the given code and system text in the specified style.\n\n Args:\n code: A string of Python code.\n system_text: A string of system text.\n style: A string specifying the style of the docstring. Can be 'google', 'numpy', or 'sphinx'.\n\n Returns:\n The Python code with docstrings added.\n \"\"\"\n system_text = system_text.format(style=style, example=_python_docstring_style[style])\n simplified_code = _simplify_python_code(code)\n documented_code = await self._aask(f\"```python\\n{simplified_code}\\n```\", [system_text])\n documented_code = OutputParser.parse_python_code(documented_code)\n return merge_docstring(code, documented_code)\n\n @staticmethod\n async def write_docstring(\n filename: str | Path, overwrite: bool = False, style: Literal[\"google\", \"numpy\", \"sphinx\"] = \"google\"\n ) -> str:\n data = await aread(str(filename))\n code = await WriteDocstring().run(data, style=style)\n if overwrite:\n await awrite(filename, code)\n return code\n\n\ndef _simplify_python_code(code: str) -> None:\n \"\"\"Simplifies the given Python code by removing expressions and the last if statement.\n\n Args:\n code: A string of Python code.\n\n Returns:\n The simplified Python code.\n \"\"\"\n code_tree = ast.parse(code)\n code_tree.body = [i for i in code_tree.body if not isinstance(i, ast.Expr)]\n if isinstance(code_tree.body[-1], ast.If):\n code_tree.body.pop()\n return ast.unparse(code_tree)\n\n\nif __name__ == \"__main__\":\n import fire\n\n fire.Fire(WriteDocstring.write_docstring)\n\n```\n\n---\nTranslate the code above into Mermaid Sequence Diagram.": "```mermaid\nsequenceDiagram\n participant User\n participant \"WriteDocstring\" as WD\n participant \"OutputParser\" as OP\n participant \"aread\" as AR\n participant \"awrite\" as AW\n\n User ->> WD: write_docstring(filename, overwrite, style)\n WD ->> AR: aread(filename)\n AR -->> WD: data\n WD ->> WD: run(data, style)\n WD ->> OP: parse_python_code(documented_code)\n OP -->> WD: documented_code\n WD ->> WD: merge_docstring(code, documented_code)\n WD ->> AW: awrite(filename, code)\n AW -->> WD: code\n WD -->> User: code\n```",
+ "\n## context\n\n### Project Name\n20240110221009\n\n### Original Requirements\n['开发一个基于大语言模型与私有知识库的搜索引擎,希望可以基于大语言模型进行搜索总结']\n\n### Search Information\n-\n\n\n-----\n\n## format example\n[CONTENT]\n{\n \"Language\": \"en_us\",\n \"Programming Language\": \"Python\",\n \"Original Requirements\": \"Create a 2048 game\",\n \"Product Goals\": [\n \"Create an engaging user experience\",\n \"Improve accessibility, be responsive\",\n \"More beautiful UI\"\n ],\n \"User Stories\": [\n \"As a player, I want to be able to choose difficulty levels\",\n \"As a player, I want to see my score after each game\",\n \"As a player, I want to get restart button when I lose\",\n \"As a player, I want to see beautiful UI that make me feel good\",\n \"As a player, I want to play game via mobile phone\"\n ],\n \"Competitive Analysis\": [\n \"2048 Game A: Simple interface, lacks responsive features\",\n \"play2048.co: Beautiful and responsive UI with my best score shown\",\n \"2048game.com: Responsive UI with my best score shown, but many ads\"\n ],\n \"Competitive Quadrant Chart\": \"quadrantChart\\n title \\\"Reach and engagement of campaigns\\\"\\n x-axis \\\"Low Reach\\\" --> \\\"High Reach\\\"\\n y-axis \\\"Low Engagement\\\" --> \\\"High Engagement\\\"\\n quadrant-1 \\\"We should expand\\\"\\n quadrant-2 \\\"Need to promote\\\"\\n quadrant-3 \\\"Re-evaluate\\\"\\n quadrant-4 \\\"May be improved\\\"\\n \\\"Campaign A\\\": [0.3, 0.6]\\n \\\"Campaign B\\\": [0.45, 0.23]\\n \\\"Campaign C\\\": [0.57, 0.69]\\n \\\"Campaign D\\\": [0.78, 0.34]\\n \\\"Campaign E\\\": [0.40, 0.34]\\n \\\"Campaign F\\\": [0.35, 0.78]\\n \\\"Our Target Product\\\": [0.5, 0.6]\",\n \"Requirement Analysis\": \"\",\n \"Requirement Pool\": [\n [\n \"P0\",\n \"The main code ...\"\n ],\n [\n \"P0\",\n \"The game algorithm ...\"\n ]\n ],\n \"UI Design draft\": \"Basic function description with a simple style and layout.\",\n \"Anything UNCLEAR\": \"\"\n}\n[/CONTENT]\n\n## nodes: \": # \"\n- Language: # Provide the language used in the project, typically matching the user's requirement language.\n- Programming Language: # Python/JavaScript or other mainstream programming language.\n- Original Requirements: # Place the original user's requirements here.\n- Product Goals: typing.List[str] # Provide up to three clear, orthogonal product goals.\n- User Stories: typing.List[str] # Provide up to 3 to 5 scenario-based user stories.\n- Competitive Analysis: typing.List[str] # Provide 5 to 7 competitive products.\n- Competitive Quadrant Chart: # Use mermaid quadrantChart syntax. Distribute scores evenly between 0 and 1\n- Requirement Analysis: # Provide a detailed analysis of the requirements.\n- Requirement Pool: typing.List[typing.List[str]] # List down the top-5 requirements with their priority (P0, P1, P2).\n- UI Design draft: # Provide a simple description of UI elements, functions, style, and layout.\n- Anything UNCLEAR: # Mention any aspects of the project that are unclear and try to clarify them.\n\n\n## constraint\nLanguage: Please use the same language as Human INPUT.\nFormat: output wrapped inside [CONTENT][/CONTENT] like format example, nothing else.\n\n## action\nFollow instructions of nodes, generate output and make sure it follows the format example.\n": "[CONTENT]\n{\n \"Language\": \"zh_cn\",\n \"Programming Language\": \"Python\",\n \"Original Requirements\": \"开发一个基于大语言模型与私有知识库的搜索引擎,希望可以基于大语言模型进行搜索总结\",\n \"Product Goals\": [],\n \"User Stories\": [],\n \"Competitive Analysis\": [],\n \"Competitive Quadrant Chart\": \"\",\n \"Requirement Analysis\": \"\",\n \"Requirement Pool\": [],\n \"UI Design draft\": \"\",\n \"Anything UNCLEAR\": \"\"\n}\n[/CONTENT]",
+ "\n## context\n\n### Project Name\n20240110221525\n\n### Original Requirements\n['需要一个基于LLM做总结的搜索引擎']\n\n### Search Information\n-\n\n\n-----\n\n## format example\n[CONTENT]\n{\n \"Language\": \"en_us\",\n \"Programming Language\": \"Python\",\n \"Original Requirements\": \"Create a 2048 game\",\n \"Product Goals\": [\n \"Create an engaging user experience\",\n \"Improve accessibility, be responsive\",\n \"More beautiful UI\"\n ],\n \"User Stories\": [\n \"As a player, I want to be able to choose difficulty levels\",\n \"As a player, I want to see my score after each game\",\n \"As a player, I want to get restart button when I lose\",\n \"As a player, I want to see beautiful UI that make me feel good\",\n \"As a player, I want to play game via mobile phone\"\n ],\n \"Competitive Analysis\": [\n \"2048 Game A: Simple interface, lacks responsive features\",\n \"play2048.co: Beautiful and responsive UI with my best score shown\",\n \"2048game.com: Responsive UI with my best score shown, but many ads\"\n ],\n \"Competitive Quadrant Chart\": \"quadrantChart\\n title \\\"Reach and engagement of campaigns\\\"\\n x-axis \\\"Low Reach\\\" --> \\\"High Reach\\\"\\n y-axis \\\"Low Engagement\\\" --> \\\"High Engagement\\\"\\n quadrant-1 \\\"We should expand\\\"\\n quadrant-2 \\\"Need to promote\\\"\\n quadrant-3 \\\"Re-evaluate\\\"\\n quadrant-4 \\\"May be improved\\\"\\n \\\"Campaign A\\\": [0.3, 0.6]\\n \\\"Campaign B\\\": [0.45, 0.23]\\n \\\"Campaign C\\\": [0.57, 0.69]\\n \\\"Campaign D\\\": [0.78, 0.34]\\n \\\"Campaign E\\\": [0.40, 0.34]\\n \\\"Campaign F\\\": [0.35, 0.78]\\n \\\"Our Target Product\\\": [0.5, 0.6]\",\n \"Requirement Analysis\": \"\",\n \"Requirement Pool\": [\n [\n \"P0\",\n \"The main code ...\"\n ],\n [\n \"P0\",\n \"The game algorithm ...\"\n ]\n ],\n \"UI Design draft\": \"Basic function description with a simple style and layout.\",\n \"Anything UNCLEAR\": \"\"\n}\n[/CONTENT]\n\n## nodes: \": # \"\n- Language: # Provide the language used in the project, typically matching the user's requirement language.\n- Programming Language: # Python/JavaScript or other mainstream programming language.\n- Original Requirements: # Place the original user's requirements here.\n- Product Goals: typing.List[str] # Provide up to three clear, orthogonal product goals.\n- User Stories: typing.List[str] # Provide up to 3 to 5 scenario-based user stories.\n- Competitive Analysis: typing.List[str] # Provide 5 to 7 competitive products.\n- Competitive Quadrant Chart: # Use mermaid quadrantChart syntax. Distribute scores evenly between 0 and 1\n- Requirement Analysis: # Provide a detailed analysis of the requirements.\n- Requirement Pool: typing.List[typing.List[str]] # List down the top-5 requirements with their priority (P0, P1, P2).\n- UI Design draft: # Provide a simple description of UI elements, functions, style, and layout.\n- Anything UNCLEAR: # Mention any aspects of the project that are unclear and try to clarify them.\n\n\n## constraint\nLanguage: Please use the same language as Human INPUT.\nFormat: output wrapped inside [CONTENT][/CONTENT] like format example, nothing else.\n\n## action\nFollow instructions of nodes, generate output and make sure it follows the format example.\n": "[CONTENT]\n{\n \"Language\": \"zh_cn\",\n \"Programming Language\": \"LLM\",\n \"Original Requirements\": \"需要一个基于LLM做总结的搜索引擎\",\n \"Product Goals\": [],\n \"User Stories\": [],\n \"Competitive Analysis\": [],\n \"Competitive Quadrant Chart\": \"\",\n \"Requirement Analysis\": \"\",\n \"Requirement Pool\": [],\n \"UI Design draft\": \"\",\n \"Anything UNCLEAR\": \"\"\n}\n[/CONTENT]",
+ "\n## context\n\n### Project Name\n20240110221737\n\n### Original Requirements\n['开发一个基于大语言模型与私有知识库的搜索引擎,希望可以基于大语言模型进行搜索总结']\n\n### Search Information\n-\n\n\n-----\n\n## format example\n[CONTENT]\n{\n \"Language\": \"en_us\",\n \"Programming Language\": \"Python\",\n \"Original Requirements\": \"Create a 2048 game\",\n \"Product Goals\": [\n \"Create an engaging user experience\",\n \"Improve accessibility, be responsive\",\n \"More beautiful UI\"\n ],\n \"User Stories\": [\n \"As a player, I want to be able to choose difficulty levels\",\n \"As a player, I want to see my score after each game\",\n \"As a player, I want to get restart button when I lose\",\n \"As a player, I want to see beautiful UI that make me feel good\",\n \"As a player, I want to play game via mobile phone\"\n ],\n \"Competitive Analysis\": [\n \"2048 Game A: Simple interface, lacks responsive features\",\n \"play2048.co: Beautiful and responsive UI with my best score shown\",\n \"2048game.com: Responsive UI with my best score shown, but many ads\"\n ],\n \"Competitive Quadrant Chart\": \"quadrantChart\\n title \\\"Reach and engagement of campaigns\\\"\\n x-axis \\\"Low Reach\\\" --> \\\"High Reach\\\"\\n y-axis \\\"Low Engagement\\\" --> \\\"High Engagement\\\"\\n quadrant-1 \\\"We should expand\\\"\\n quadrant-2 \\\"Need to promote\\\"\\n quadrant-3 \\\"Re-evaluate\\\"\\n quadrant-4 \\\"May be improved\\\"\\n \\\"Campaign A\\\": [0.3, 0.6]\\n \\\"Campaign B\\\": [0.45, 0.23]\\n \\\"Campaign C\\\": [0.57, 0.69]\\n \\\"Campaign D\\\": [0.78, 0.34]\\n \\\"Campaign E\\\": [0.40, 0.34]\\n \\\"Campaign F\\\": [0.35, 0.78]\\n \\\"Our Target Product\\\": [0.5, 0.6]\",\n \"Requirement Analysis\": \"\",\n \"Requirement Pool\": [\n [\n \"P0\",\n \"The main code ...\"\n ],\n [\n \"P0\",\n \"The game algorithm ...\"\n ]\n ],\n \"UI Design draft\": \"Basic function description with a simple style and layout.\",\n \"Anything UNCLEAR\": \"\"\n}\n[/CONTENT]\n\n## nodes: \": # \"\n- Language: # Provide the language used in the project, typically matching the user's requirement language.\n- Programming Language: # Python/JavaScript or other mainstream programming language.\n- Original Requirements: # Place the original user's requirements here.\n- Product Goals: typing.List[str] # Provide up to three clear, orthogonal product goals.\n- User Stories: typing.List[str] # Provide up to 3 to 5 scenario-based user stories.\n- Competitive Analysis: typing.List[str] # Provide 5 to 7 competitive products.\n- Competitive Quadrant Chart: # Use mermaid quadrantChart syntax. Distribute scores evenly between 0 and 1\n- Requirement Analysis: # Provide a detailed analysis of the requirements.\n- Requirement Pool: typing.List[typing.List[str]] # List down the top-5 requirements with their priority (P0, P1, P2).\n- UI Design draft: # Provide a simple description of UI elements, functions, style, and layout.\n- Anything UNCLEAR: # Mention any aspects of the project that are unclear and try to clarify them.\n\n\n## constraint\nLanguage: Please use the same language as Human INPUT.\nFormat: output wrapped inside [CONTENT][/CONTENT] like format example, nothing else.\n\n## action\nFollow instructions of nodes, generate output and make sure it follows the format example.\n": "[CONTENT]\n{\n \"Language\": \"zh_cn\",\n \"Programming Language\": \"Python\",\n \"Original Requirements\": \"开发一个基于大语言模型与私有知识库的搜索引擎,希望可以基于大语言模型进行搜索总结\",\n \"Product Goals\": [],\n \"User Stories\": [],\n \"Competitive Analysis\": [],\n \"Competitive Quadrant Chart\": \"\",\n \"Requirement Analysis\": \"\",\n \"Requirement Pool\": [],\n \"UI Design draft\": \"\",\n \"Anything UNCLEAR\": \"\"\n}\n[/CONTENT]",
+ "\n## context\n\n### Project Name\n20240110221737\n\n### Original Requirements\n['']\n\n### Search Information\n-\n\n\n-----\n\n## format example\n[CONTENT]\n{\n \"Language\": \"en_us\",\n \"Programming Language\": \"Python\",\n \"Original Requirements\": \"Create a 2048 game\",\n \"Product Goals\": [\n \"Create an engaging user experience\",\n \"Improve accessibility, be responsive\",\n \"More beautiful UI\"\n ],\n \"User Stories\": [\n \"As a player, I want to be able to choose difficulty levels\",\n \"As a player, I want to see my score after each game\",\n \"As a player, I want to get restart button when I lose\",\n \"As a player, I want to see beautiful UI that make me feel good\",\n \"As a player, I want to play game via mobile phone\"\n ],\n \"Competitive Analysis\": [\n \"2048 Game A: Simple interface, lacks responsive features\",\n \"play2048.co: Beautiful and responsive UI with my best score shown\",\n \"2048game.com: Responsive UI with my best score shown, but many ads\"\n ],\n \"Competitive Quadrant Chart\": \"quadrantChart\\n title \\\"Reach and engagement of campaigns\\\"\\n x-axis \\\"Low Reach\\\" --> \\\"High Reach\\\"\\n y-axis \\\"Low Engagement\\\" --> \\\"High Engagement\\\"\\n quadrant-1 \\\"We should expand\\\"\\n quadrant-2 \\\"Need to promote\\\"\\n quadrant-3 \\\"Re-evaluate\\\"\\n quadrant-4 \\\"May be improved\\\"\\n \\\"Campaign A\\\": [0.3, 0.6]\\n \\\"Campaign B\\\": [0.45, 0.23]\\n \\\"Campaign C\\\": [0.57, 0.69]\\n \\\"Campaign D\\\": [0.78, 0.34]\\n \\\"Campaign E\\\": [0.40, 0.34]\\n \\\"Campaign F\\\": [0.35, 0.78]\\n \\\"Our Target Product\\\": [0.5, 0.6]\",\n \"Requirement Analysis\": \"\",\n \"Requirement Pool\": [\n [\n \"P0\",\n \"The main code ...\"\n ],\n [\n \"P0\",\n \"The game algorithm ...\"\n ]\n ],\n \"UI Design draft\": \"Basic function description with a simple style and layout.\",\n \"Anything UNCLEAR\": \"\"\n}\n[/CONTENT]\n\n## nodes: \": # \"\n- Language: # Provide the language used in the project, typically matching the user's requirement language.\n- Programming Language: # Python/JavaScript or other mainstream programming language.\n- Original Requirements: # Place the original user's requirements here.\n- Product Goals: typing.List[str] # Provide up to three clear, orthogonal product goals.\n- User Stories: typing.List[str] # Provide up to 3 to 5 scenario-based user stories.\n- Competitive Analysis: typing.List[str] # Provide 5 to 7 competitive products.\n- Competitive Quadrant Chart: # Use mermaid quadrantChart syntax. Distribute scores evenly between 0 and 1\n- Requirement Analysis: # Provide a detailed analysis of the requirements.\n- Requirement Pool: typing.List[typing.List[str]] # List down the top-5 requirements with their priority (P0, P1, P2).\n- UI Design draft: # Provide a simple description of UI elements, functions, style, and layout.\n- Anything UNCLEAR: # Mention any aspects of the project that are unclear and try to clarify them.\n\n\n## constraint\nLanguage: Please use the same language as Human INPUT.\nFormat: output wrapped inside [CONTENT][/CONTENT] like format example, nothing else.\n\n## action\nFollow instructions of nodes, generate output and make sure it follows the format example.\n": "[CONTENT]\n{\n \"Language\": \"en_us\",\n \"Programming Language\": \"Python\",\n \"Original Requirements\": \"\",\n \"Product Goals\": [],\n \"User Stories\": [],\n \"Competitive Analysis\": [],\n \"Competitive Quadrant Chart\": \"\",\n \"Requirement Analysis\": \"\",\n \"Requirement Pool\": [],\n \"UI Design draft\": \"\",\n \"Anything UNCLEAR\": \"\"\n}\n[/CONTENT]",
+ "\n## context\n\n### Project Name\n20240111154514\n\n### Original Requirements\n['需要一个基于LLM做总结的搜索引擎']\n\n### Search Information\n-\n\n\n-----\n\n## format example\n[CONTENT]\n{\n \"Language\": \"en_us\",\n \"Programming Language\": \"Python\",\n \"Original Requirements\": \"Create a 2048 game\",\n \"Product Goals\": [\n \"Create an engaging user experience\",\n \"Improve accessibility, be responsive\",\n \"More beautiful UI\"\n ],\n \"User Stories\": [\n \"As a player, I want to be able to choose difficulty levels\",\n \"As a player, I want to see my score after each game\",\n \"As a player, I want to get restart button when I lose\",\n \"As a player, I want to see beautiful UI that make me feel good\",\n \"As a player, I want to play game via mobile phone\"\n ],\n \"Competitive Analysis\": [\n \"2048 Game A: Simple interface, lacks responsive features\",\n \"play2048.co: Beautiful and responsive UI with my best score shown\",\n \"2048game.com: Responsive UI with my best score shown, but many ads\"\n ],\n \"Competitive Quadrant Chart\": \"quadrantChart\\n title \\\"Reach and engagement of campaigns\\\"\\n x-axis \\\"Low Reach\\\" --> \\\"High Reach\\\"\\n y-axis \\\"Low Engagement\\\" --> \\\"High Engagement\\\"\\n quadrant-1 \\\"We should expand\\\"\\n quadrant-2 \\\"Need to promote\\\"\\n quadrant-3 \\\"Re-evaluate\\\"\\n quadrant-4 \\\"May be improved\\\"\\n \\\"Campaign A\\\": [0.3, 0.6]\\n \\\"Campaign B\\\": [0.45, 0.23]\\n \\\"Campaign C\\\": [0.57, 0.69]\\n \\\"Campaign D\\\": [0.78, 0.34]\\n \\\"Campaign E\\\": [0.40, 0.34]\\n \\\"Campaign F\\\": [0.35, 0.78]\\n \\\"Our Target Product\\\": [0.5, 0.6]\",\n \"Requirement Analysis\": \"\",\n \"Requirement Pool\": [\n [\n \"P0\",\n \"The main code ...\"\n ],\n [\n \"P0\",\n \"The game algorithm ...\"\n ]\n ],\n \"UI Design draft\": \"Basic function description with a simple style and layout.\",\n \"Anything UNCLEAR\": \"\"\n}\n[/CONTENT]\n\n## nodes: \": # \"\n- Language: # Provide the language used in the project, typically matching the user's requirement language.\n- Programming Language: # Python/JavaScript or other mainstream programming language.\n- Original Requirements: # Place the original user's requirements here.\n- Product Goals: typing.List[str] # Provide up to three clear, orthogonal product goals.\n- User Stories: typing.List[str] # Provide up to 3 to 5 scenario-based user stories.\n- Competitive Analysis: typing.List[str] # Provide 5 to 7 competitive products.\n- Competitive Quadrant Chart: # Use mermaid quadrantChart syntax. Distribute scores evenly between 0 and 1\n- Requirement Analysis: # Provide a detailed analysis of the requirements.\n- Requirement Pool: typing.List[typing.List[str]] # List down the top-5 requirements with their priority (P0, P1, P2).\n- UI Design draft: # Provide a simple description of UI elements, functions, style, and layout.\n- Anything UNCLEAR: # Mention any aspects of the project that are unclear and try to clarify them.\n\n\n## constraint\nLanguage: Please use the same language as Human INPUT.\nFormat: output wrapped inside [CONTENT][/CONTENT] like format example, nothing else.\n\n## action\nFollow instructions of nodes, generate output and make sure it follows the format example.\n": "[CONTENT]\n{\n \"Language\": \"zh_cn\",\n \"Programming Language\": \"LLM\",\n \"Original Requirements\": \"需要一个基于LLM做总结的搜索引擎\",\n \"Product Goals\": [],\n \"User Stories\": [],\n \"Competitive Analysis\": [],\n \"Competitive Quadrant Chart\": \"\",\n \"Requirement Analysis\": \"\",\n \"Requirement Pool\": [],\n \"UI Design draft\": \"\",\n \"Anything UNCLEAR\": \"\"\n}\n[/CONTENT]",
+ "You are a python code to Mermaid Sequence Diagram translator in function detail#SYSTEM_MSG_END#```python\n#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\"\"\"\n@Time : 2023/5/23 18:27\n@Author : alexanderwu\n@File : search_engine_serpapi.py\n\"\"\"\nfrom typing import Any, Dict, Optional, Tuple\n\nimport aiohttp\nfrom pydantic import BaseModel, ConfigDict, Field, field_validator\n\nfrom metagpt.config2 import config\n\n\nclass SerpAPIWrapper(BaseModel):\n model_config = ConfigDict(arbitrary_types_allowed=True)\n\n search_engine: Any = None #: :meta private:\n params: dict = Field(\n default_factory=lambda: {\n \"engine\": \"google\",\n \"google_domain\": \"google.com\",\n \"gl\": \"us\",\n \"hl\": \"en\",\n }\n )\n # should add `validate_default=True` to check with default value\n serpapi_api_key: Optional[str] = Field(default=None, validate_default=True)\n aiosession: Optional[aiohttp.ClientSession] = None\n\n @field_validator(\"serpapi_api_key\", mode=\"before\")\n @classmethod\n def check_serpapi_api_key(cls, val: str):\n val = val or config.search.api_key\n if not val:\n raise ValueError(\n \"To use, make sure you provide the serpapi_api_key when constructing an object. Alternatively, \"\n \"ensure that the environment variable SERPAPI_API_KEY is set with your API key. You can obtain \"\n \"an API key from https://serpapi.com/.\"\n )\n return val\n\n async def run(self, query, max_results: int = 8, as_string: bool = True, **kwargs: Any) -> str:\n \"\"\"Run query through SerpAPI and parse result async.\"\"\"\n result = await self.results(query, max_results)\n return self._process_response(result, as_string=as_string)\n\n async def results(self, query: str, max_results: int) -> dict:\n \"\"\"Use aiohttp to run query through SerpAPI and return the results async.\"\"\"\n\n def construct_url_and_params() -> Tuple[str, Dict[str, str]]:\n params = self.get_params(query)\n params[\"source\"] = \"python\"\n params[\"num\"] = max_results\n params[\"output\"] = \"json\"\n url = \"https://serpapi.com/search\"\n return url, params\n\n url, params = construct_url_and_params()\n if not self.aiosession:\n async with aiohttp.ClientSession() as session:\n async with session.get(url, params=params) as response:\n res = await response.json()\n else:\n async with self.aiosession.get(url, params=params) as response:\n res = await response.json()\n\n return res\n\n def get_params(self, query: str) -> Dict[str, str]:\n \"\"\"Get parameters for SerpAPI.\"\"\"\n _params = {\n \"api_key\": self.serpapi_api_key,\n \"q\": query,\n }\n params = {**self.params, **_params}\n return params\n\n @staticmethod\n def _process_response(res: dict, as_string: bool) -> str:\n \"\"\"Process response from SerpAPI.\"\"\"\n # logger.debug(res)\n focus = [\"title\", \"snippet\", \"link\"]\n get_focused = lambda x: {i: j for i, j in x.items() if i in focus}\n\n if \"error\" in res.keys():\n raise ValueError(f\"Got error from SerpAPI: {res['error']}\")\n if \"answer_box\" in res.keys() and \"answer\" in res[\"answer_box\"].keys():\n toret = res[\"answer_box\"][\"answer\"]\n elif \"answer_box\" in res.keys() and \"snippet\" in res[\"answer_box\"].keys():\n toret = res[\"answer_box\"][\"snippet\"]\n elif \"answer_box\" in res.keys() and \"snippet_highlighted_words\" in res[\"answer_box\"].keys():\n toret = res[\"answer_box\"][\"snippet_highlighted_words\"][0]\n elif \"sports_results\" in res.keys() and \"game_spotlight\" in res[\"sports_results\"].keys():\n toret = res[\"sports_results\"][\"game_spotlight\"]\n elif \"knowledge_graph\" in res.keys() and \"description\" in res[\"knowledge_graph\"].keys():\n toret = res[\"knowledge_graph\"][\"description\"]\n elif \"snippet\" in res[\"organic_results\"][0].keys():\n toret = res[\"organic_results\"][0][\"snippet\"]\n else:\n toret = \"No good search result found\"\n\n toret_l = []\n if \"answer_box\" in res.keys() and \"snippet\" in res[\"answer_box\"].keys():\n toret_l += [get_focused(res[\"answer_box\"])]\n if res.get(\"organic_results\"):\n toret_l += [get_focused(i) for i in res.get(\"organic_results\")]\n\n return str(toret) + \"\\n\" + str(toret_l) if as_string else toret_l\n\n\nif __name__ == \"__main__\":\n import fire\n\n fire.Fire(SerpAPIWrapper().run)\n\n```\n\n---\nTranslate the code above into Mermaid Sequence Diagram.": "```mermaid\nsequenceDiagram\n participant SerpAPIWrapper\n participant aiohttp\n participant config\n participant session\n participant response\n participant fire\n\n Note over SerpAPIWrapper: Initialization\n SerpAPIWrapper->>config: get search.api_key\n config-->>SerpAPIWrapper: return search.api_key\n SerpAPIWrapper->>SerpAPIWrapper: check_serpapi_api_key()\n SerpAPIWrapper->>SerpAPIWrapper: get_params()\n SerpAPIWrapper->>SerpAPIWrapper: results()\n SerpAPIWrapper->>aiohttp: ClientSession()\n aiohttp->>session: get(url, params)\n session->>response: json()\n response-->>session: return json response\n session-->>aiohttp: return json response\n aiohttp-->>SerpAPIWrapper: return json response\n SerpAPIWrapper-->>SerpAPIWrapper: _process_response()\n SerpAPIWrapper-->>fire: run()\n```",
+ "You are a python code to Mermaid Sequence Diagram translator in function detail#SYSTEM_MSG_END#```python\n#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\"\"\"\n@Time : 2023/5/23 18:27\n@Author : alexanderwu\n@File : search_engine_serpapi.py\n\"\"\"\nimport json\nfrom typing import Any, Dict, Optional, Tuple\n\nimport aiohttp\nfrom pydantic import BaseModel, ConfigDict, Field, field_validator\n\nfrom metagpt.config2 import config\n\n\nclass SerperWrapper(BaseModel):\n model_config = ConfigDict(arbitrary_types_allowed=True)\n\n search_engine: Any = None #: :meta private:\n payload: dict = Field(default_factory=lambda: {\"page\": 1, \"num\": 10})\n serper_api_key: Optional[str] = Field(default=None, validate_default=True)\n aiosession: Optional[aiohttp.ClientSession] = None\n\n @field_validator(\"serper_api_key\", mode=\"before\")\n @classmethod\n def check_serper_api_key(cls, val: str):\n val = val or config.search.api_key\n if not val:\n raise ValueError(\n \"To use, make sure you provide the serper_api_key when constructing an object. Alternatively, \"\n \"ensure that the environment variable SERPER_API_KEY is set with your API key. You can obtain \"\n \"an API key from https://serper.dev/.\"\n )\n return val\n\n async def run(self, query: str, max_results: int = 8, as_string: bool = True, **kwargs: Any) -> str:\n \"\"\"Run query through Serper and parse result async.\"\"\"\n if isinstance(query, str):\n return self._process_response((await self.results([query], max_results))[0], as_string=as_string)\n else:\n results = [self._process_response(res, as_string) for res in await self.results(query, max_results)]\n return \"\\n\".join(results) if as_string else results\n\n async def results(self, queries: list[str], max_results: int = 8) -> dict:\n \"\"\"Use aiohttp to run query through Serper and return the results async.\"\"\"\n\n def construct_url_and_payload_and_headers() -> Tuple[str, Dict[str, str]]:\n payloads = self.get_payloads(queries, max_results)\n url = \"https://google.serper.dev/search\"\n headers = self.get_headers()\n return url, payloads, headers\n\n url, payloads, headers = construct_url_and_payload_and_headers()\n if not self.aiosession:\n async with aiohttp.ClientSession() as session:\n async with session.post(url, data=payloads, headers=headers) as response:\n res = await response.json()\n else:\n async with self.aiosession.get.post(url, data=payloads, headers=headers) as response:\n res = await response.json()\n\n return res\n\n def get_payloads(self, queries: list[str], max_results: int) -> Dict[str, str]:\n \"\"\"Get payloads for Serper.\"\"\"\n payloads = []\n for query in queries:\n _payload = {\n \"q\": query,\n \"num\": max_results,\n }\n payloads.append({**self.payload, **_payload})\n return json.dumps(payloads, sort_keys=True)\n\n def get_headers(self) -> Dict[str, str]:\n headers = {\"X-API-KEY\": self.serper_api_key, \"Content-Type\": \"application/json\"}\n return headers\n\n @staticmethod\n def _process_response(res: dict, as_string: bool = False) -> str:\n \"\"\"Process response from SerpAPI.\"\"\"\n # logger.debug(res)\n focus = [\"title\", \"snippet\", \"link\"]\n\n def get_focused(x):\n return {i: j for i, j in x.items() if i in focus}\n\n if \"error\" in res.keys():\n raise ValueError(f\"Got error from SerpAPI: {res['error']}\")\n if \"answer_box\" in res.keys() and \"answer\" in res[\"answer_box\"].keys():\n toret = res[\"answer_box\"][\"answer\"]\n elif \"answer_box\" in res.keys() and \"snippet\" in res[\"answer_box\"].keys():\n toret = res[\"answer_box\"][\"snippet\"]\n elif \"answer_box\" in res.keys() and \"snippet_highlighted_words\" in res[\"answer_box\"].keys():\n toret = res[\"answer_box\"][\"snippet_highlighted_words\"][0]\n elif \"sports_results\" in res.keys() and \"game_spotlight\" in res[\"sports_results\"].keys():\n toret = res[\"sports_results\"][\"game_spotlight\"]\n elif \"knowledge_graph\" in res.keys() and \"description\" in res[\"knowledge_graph\"].keys():\n toret = res[\"knowledge_graph\"][\"description\"]\n elif \"snippet\" in res[\"organic\"][0].keys():\n toret = res[\"organic\"][0][\"snippet\"]\n else:\n toret = \"No good search result found\"\n\n toret_l = []\n if \"answer_box\" in res.keys() and \"snippet\" in res[\"answer_box\"].keys():\n toret_l += [get_focused(res[\"answer_box\"])]\n if res.get(\"organic\"):\n toret_l += [get_focused(i) for i in res.get(\"organic\")]\n\n return str(toret) + \"\\n\" + str(toret_l) if as_string else toret_l\n\n\nif __name__ == \"__main__\":\n import fire\n\n fire.Fire(SerperWrapper().run)\n\n```\n\n---\nTranslate the code above into Mermaid Sequence Diagram.": "```mermaid\nsequenceDiagram\n participant User\n participant SerperWrapper\n participant aiohttp\n participant config\n\n User ->> SerperWrapper: run(query, max_results, as_string, **kwargs)\n SerperWrapper ->> SerperWrapper: _process_response(response, as_string)\n SerperWrapper ->> SerperWrapper: results(queries, max_results)\n SerperWrapper ->> aiohttp: post(url, data, headers)\n aiohttp ->> SerperWrapper: response\n SerperWrapper ->> User: return result\n SerperWrapper ->> config: search.api_key\n```",
+ "You are a python code to Mermaid Sequence Diagram translator in function detail#SYSTEM_MSG_END#```python\n#!/usr/bin/env python\n# -*- coding: utf-8 -*-\nfrom __future__ import annotations\n\nimport asyncio\nimport json\nfrom concurrent import futures\nfrom typing import Optional\nfrom urllib.parse import urlparse\n\nimport httplib2\nfrom pydantic import BaseModel, ConfigDict, Field, field_validator\n\nfrom metagpt.config2 import config\nfrom metagpt.logs import logger\n\ntry:\n from googleapiclient.discovery import build\n from googleapiclient.errors import HttpError\nexcept ImportError:\n raise ImportError(\n \"To use this module, you should have the `google-api-python-client` Python package installed. \"\n \"You can install it by running the command: `pip install -e.[search-google]`\"\n )\n\n\nclass GoogleAPIWrapper(BaseModel):\n model_config = ConfigDict(arbitrary_types_allowed=True)\n\n google_api_key: Optional[str] = Field(default=None, validate_default=True)\n google_cse_id: Optional[str] = Field(default=None, validate_default=True)\n loop: Optional[asyncio.AbstractEventLoop] = None\n executor: Optional[futures.Executor] = None\n\n @field_validator(\"google_api_key\", mode=\"before\")\n @classmethod\n def check_google_api_key(cls, val: str):\n val = val or config.search.api_key\n if not val:\n raise ValueError(\n \"To use, make sure you provide the google_api_key when constructing an object. Alternatively, \"\n \"ensure that the environment variable GOOGLE_API_KEY is set with your API key. You can obtain \"\n \"an API key from https://console.cloud.google.com/apis/credentials.\"\n )\n return val\n\n @field_validator(\"google_cse_id\", mode=\"before\")\n @classmethod\n def check_google_cse_id(cls, val: str):\n val = val or config.search.cse_id\n if not val:\n raise ValueError(\n \"To use, make sure you provide the google_cse_id when constructing an object. Alternatively, \"\n \"ensure that the environment variable GOOGLE_CSE_ID is set with your API key. You can obtain \"\n \"an API key from https://programmablesearchengine.google.com/controlpanel/create.\"\n )\n return val\n\n @property\n def google_api_client(self):\n build_kwargs = {\"developerKey\": self.google_api_key}\n if config.proxy:\n parse_result = urlparse(config.proxy)\n proxy_type = parse_result.scheme\n if proxy_type == \"https\":\n proxy_type = \"http\"\n build_kwargs[\"http\"] = httplib2.Http(\n proxy_info=httplib2.ProxyInfo(\n getattr(httplib2.socks, f\"PROXY_TYPE_{proxy_type.upper()}\"),\n parse_result.hostname,\n parse_result.port,\n ),\n )\n service = build(\"customsearch\", \"v1\", **build_kwargs)\n return service.cse()\n\n async def run(\n self,\n query: str,\n max_results: int = 8,\n as_string: bool = True,\n focus: list[str] | None = None,\n ) -> str | list[dict]:\n \"\"\"Return the results of a Google search using the official Google API.\n\n Args:\n query: The search query.\n max_results: The number of results to return.\n as_string: A boolean flag to determine the return type of the results. If True, the function will\n return a formatted string with the search results. If False, it will return a list of dictionaries\n containing detailed information about each search result.\n focus: Specific information to be focused on from each search result.\n\n Returns:\n The results of the search.\n \"\"\"\n loop = self.loop or asyncio.get_event_loop()\n future = loop.run_in_executor(\n self.executor, self.google_api_client.list(q=query, num=max_results, cx=self.google_cse_id).execute\n )\n try:\n result = await future\n # Extract the search result items from the response\n search_results = result.get(\"items\", [])\n\n except HttpError as e:\n # Handle errors in the API call\n logger.exception(f\"fail to search {query} for {e}\")\n search_results = []\n\n focus = focus or [\"snippet\", \"link\", \"title\"]\n details = [{i: j for i, j in item_dict.items() if i in focus} for item_dict in search_results]\n # Return the list of search result URLs\n if as_string:\n return safe_google_results(details)\n\n return details\n\n\ndef safe_google_results(results: str | list) -> str:\n \"\"\"Return the results of a google search in a safe format.\n\n Args:\n results: The search results.\n\n Returns:\n The results of the search.\n \"\"\"\n if isinstance(results, list):\n safe_message = json.dumps([result for result in results])\n else:\n safe_message = results.encode(\"utf-8\", \"ignore\").decode(\"utf-8\")\n return safe_message\n\n\nif __name__ == \"__main__\":\n import fire\n\n fire.Fire(GoogleAPIWrapper().run)\n\n```\n\n---\nTranslate the code above into Mermaid Sequence Diagram.": "```mermaid\nsequenceDiagram\n participant BaseModel\n participant httplib2\n participant asyncio\n participant futures\n participant urlparse\n participant json\n participant config\n participant logger\n participant googleapiclient.discovery\n participant googleapiclient.errors\n participant fire\n\n BaseModel->>ConfigDict: model_config\n BaseModel->>Optional: google_api_key\n BaseModel->>Optional: google_cse_id\n BaseModel->>Optional: loop\n BaseModel->>Optional: executor\n BaseModel->>googleapiclient.discovery: check_google_api_key\n BaseModel->>googleapiclient.discovery: check_google_cse_id\n BaseModel->>googleapiclient.discovery: google_api_client\n BaseModel->>asyncio: run\n asyncio->>futures: run_in_executor\n futures->>googleapiclient.discovery: list\n googleapiclient.discovery->>googleapiclient.discovery: execute\n googleapiclient.discovery-->>futures: result\n futures-->>asyncio: result\n asyncio-->>BaseModel: result\n BaseModel-->>BaseModel: safe_google_results\n BaseModel-->>BaseModel: run\n BaseModel-->>fire: run\n```",
+ "\n## context\n\n### Project Name\n20240111154819\n\n### Original Requirements\n['开发一个基于大语言模型与私有知识库的搜索引擎,希望可以基于大语言模型进行搜索总结']\n\n### Search Information\n-\n\n\n-----\n\n## format example\n[CONTENT]\n{\n \"Language\": \"en_us\",\n \"Programming Language\": \"Python\",\n \"Original Requirements\": \"Create a 2048 game\",\n \"Product Goals\": [\n \"Create an engaging user experience\",\n \"Improve accessibility, be responsive\",\n \"More beautiful UI\"\n ],\n \"User Stories\": [\n \"As a player, I want to be able to choose difficulty levels\",\n \"As a player, I want to see my score after each game\",\n \"As a player, I want to get restart button when I lose\",\n \"As a player, I want to see beautiful UI that make me feel good\",\n \"As a player, I want to play game via mobile phone\"\n ],\n \"Competitive Analysis\": [\n \"2048 Game A: Simple interface, lacks responsive features\",\n \"play2048.co: Beautiful and responsive UI with my best score shown\",\n \"2048game.com: Responsive UI with my best score shown, but many ads\"\n ],\n \"Competitive Quadrant Chart\": \"quadrantChart\\n title \\\"Reach and engagement of campaigns\\\"\\n x-axis \\\"Low Reach\\\" --> \\\"High Reach\\\"\\n y-axis \\\"Low Engagement\\\" --> \\\"High Engagement\\\"\\n quadrant-1 \\\"We should expand\\\"\\n quadrant-2 \\\"Need to promote\\\"\\n quadrant-3 \\\"Re-evaluate\\\"\\n quadrant-4 \\\"May be improved\\\"\\n \\\"Campaign A\\\": [0.3, 0.6]\\n \\\"Campaign B\\\": [0.45, 0.23]\\n \\\"Campaign C\\\": [0.57, 0.69]\\n \\\"Campaign D\\\": [0.78, 0.34]\\n \\\"Campaign E\\\": [0.40, 0.34]\\n \\\"Campaign F\\\": [0.35, 0.78]\\n \\\"Our Target Product\\\": [0.5, 0.6]\",\n \"Requirement Analysis\": \"\",\n \"Requirement Pool\": [\n [\n \"P0\",\n \"The main code ...\"\n ],\n [\n \"P0\",\n \"The game algorithm ...\"\n ]\n ],\n \"UI Design draft\": \"Basic function description with a simple style and layout.\",\n \"Anything UNCLEAR\": \"\"\n}\n[/CONTENT]\n\n## nodes: \": # \"\n- Language: # Provide the language used in the project, typically matching the user's requirement language.\n- Programming Language: # Python/JavaScript or other mainstream programming language.\n- Original Requirements: # Place the original user's requirements here.\n- Product Goals: typing.List[str] # Provide up to three clear, orthogonal product goals.\n- User Stories: typing.List[str] # Provide up to 3 to 5 scenario-based user stories.\n- Competitive Analysis: typing.List[str] # Provide 5 to 7 competitive products.\n- Competitive Quadrant Chart: # Use mermaid quadrantChart syntax. Distribute scores evenly between 0 and 1\n- Requirement Analysis: # Provide a detailed analysis of the requirements.\n- Requirement Pool: typing.List[typing.List[str]] # List down the top-5 requirements with their priority (P0, P1, P2).\n- UI Design draft: # Provide a simple description of UI elements, functions, style, and layout.\n- Anything UNCLEAR: # Mention any aspects of the project that are unclear and try to clarify them.\n\n\n## constraint\nLanguage: Please use the same language as Human INPUT.\nFormat: output wrapped inside [CONTENT][/CONTENT] like format example, nothing else.\n\n## action\nFollow instructions of nodes, generate output and make sure it follows the format example.\n": "[CONTENT]\n{\n \"Language\": \"zh_cn\",\n \"Programming Language\": \"Python\",\n \"Original Requirements\": \"开发一个基于大语言模型与私有知识库的搜索引擎,希望可以基于大语言模型进行搜索总结\",\n \"Product Goals\": [],\n \"User Stories\": [],\n \"Competitive Analysis\": [],\n \"Competitive Quadrant Chart\": \"\",\n \"Requirement Analysis\": \"\",\n \"Requirement Pool\": [],\n \"UI Design draft\": \"\",\n \"Anything UNCLEAR\": \"\"\n}\n[/CONTENT]",
+ "\n## context\n\n### Project Name\n20240111154819\n\n### Original Requirements\n['']\n\n### Search Information\n-\n\n\n-----\n\n## format example\n[CONTENT]\n{\n \"Language\": \"en_us\",\n \"Programming Language\": \"Python\",\n \"Original Requirements\": \"Create a 2048 game\",\n \"Product Goals\": [\n \"Create an engaging user experience\",\n \"Improve accessibility, be responsive\",\n \"More beautiful UI\"\n ],\n \"User Stories\": [\n \"As a player, I want to be able to choose difficulty levels\",\n \"As a player, I want to see my score after each game\",\n \"As a player, I want to get restart button when I lose\",\n \"As a player, I want to see beautiful UI that make me feel good\",\n \"As a player, I want to play game via mobile phone\"\n ],\n \"Competitive Analysis\": [\n \"2048 Game A: Simple interface, lacks responsive features\",\n \"play2048.co: Beautiful and responsive UI with my best score shown\",\n \"2048game.com: Responsive UI with my best score shown, but many ads\"\n ],\n \"Competitive Quadrant Chart\": \"quadrantChart\\n title \\\"Reach and engagement of campaigns\\\"\\n x-axis \\\"Low Reach\\\" --> \\\"High Reach\\\"\\n y-axis \\\"Low Engagement\\\" --> \\\"High Engagement\\\"\\n quadrant-1 \\\"We should expand\\\"\\n quadrant-2 \\\"Need to promote\\\"\\n quadrant-3 \\\"Re-evaluate\\\"\\n quadrant-4 \\\"May be improved\\\"\\n \\\"Campaign A\\\": [0.3, 0.6]\\n \\\"Campaign B\\\": [0.45, 0.23]\\n \\\"Campaign C\\\": [0.57, 0.69]\\n \\\"Campaign D\\\": [0.78, 0.34]\\n \\\"Campaign E\\\": [0.40, 0.34]\\n \\\"Campaign F\\\": [0.35, 0.78]\\n \\\"Our Target Product\\\": [0.5, 0.6]\",\n \"Requirement Analysis\": \"\",\n \"Requirement Pool\": [\n [\n \"P0\",\n \"The main code ...\"\n ],\n [\n \"P0\",\n \"The game algorithm ...\"\n ]\n ],\n \"UI Design draft\": \"Basic function description with a simple style and layout.\",\n \"Anything UNCLEAR\": \"\"\n}\n[/CONTENT]\n\n## nodes: \": # \"\n- Language: # Provide the language used in the project, typically matching the user's requirement language.\n- Programming Language: # Python/JavaScript or other mainstream programming language.\n- Original Requirements: # Place the original user's requirements here.\n- Product Goals: typing.List[str] # Provide up to three clear, orthogonal product goals.\n- User Stories: typing.List[str] # Provide up to 3 to 5 scenario-based user stories.\n- Competitive Analysis: typing.List[str] # Provide 5 to 7 competitive products.\n- Competitive Quadrant Chart: # Use mermaid quadrantChart syntax. Distribute scores evenly between 0 and 1\n- Requirement Analysis: # Provide a detailed analysis of the requirements.\n- Requirement Pool: typing.List[typing.List[str]] # List down the top-5 requirements with their priority (P0, P1, P2).\n- UI Design draft: # Provide a simple description of UI elements, functions, style, and layout.\n- Anything UNCLEAR: # Mention any aspects of the project that are unclear and try to clarify them.\n\n\n## constraint\nLanguage: Please use the same language as Human INPUT.\nFormat: output wrapped inside [CONTENT][/CONTENT] like format example, nothing else.\n\n## action\nFollow instructions of nodes, generate output and make sure it follows the format example.\n": "[CONTENT]\n{\n \"Language\": \"en_us\",\n \"Programming Language\": \"Python\",\n \"Original Requirements\": \"\",\n \"Product Goals\": [],\n \"User Stories\": [],\n \"Competitive Analysis\": [],\n \"Competitive Quadrant Chart\": \"\",\n \"Requirement Analysis\": \"\",\n \"Requirement Pool\": [],\n \"UI Design draft\": \"\",\n \"Anything UNCLEAR\": \"\"\n}\n[/CONTENT]",
+ "\n## context\n\n### Project Name\n20240111180901\n\n### Original Requirements\n['需要一个基于LLM做总结的搜索引擎']\n\n### Search Information\n-\n\n\n-----\n\n## format example\n[CONTENT]\n{\n \"Language\": \"en_us\",\n \"Programming Language\": \"Python\",\n \"Original Requirements\": \"Create a 2048 game\",\n \"Product Goals\": [\n \"Create an engaging user experience\",\n \"Improve accessibility, be responsive\",\n \"More beautiful UI\"\n ],\n \"User Stories\": [\n \"As a player, I want to be able to choose difficulty levels\",\n \"As a player, I want to see my score after each game\",\n \"As a player, I want to get restart button when I lose\",\n \"As a player, I want to see beautiful UI that make me feel good\",\n \"As a player, I want to play game via mobile phone\"\n ],\n \"Competitive Analysis\": [\n \"2048 Game A: Simple interface, lacks responsive features\",\n \"play2048.co: Beautiful and responsive UI with my best score shown\",\n \"2048game.com: Responsive UI with my best score shown, but many ads\"\n ],\n \"Competitive Quadrant Chart\": \"quadrantChart\\n title \\\"Reach and engagement of campaigns\\\"\\n x-axis \\\"Low Reach\\\" --> \\\"High Reach\\\"\\n y-axis \\\"Low Engagement\\\" --> \\\"High Engagement\\\"\\n quadrant-1 \\\"We should expand\\\"\\n quadrant-2 \\\"Need to promote\\\"\\n quadrant-3 \\\"Re-evaluate\\\"\\n quadrant-4 \\\"May be improved\\\"\\n \\\"Campaign A\\\": [0.3, 0.6]\\n \\\"Campaign B\\\": [0.45, 0.23]\\n \\\"Campaign C\\\": [0.57, 0.69]\\n \\\"Campaign D\\\": [0.78, 0.34]\\n \\\"Campaign E\\\": [0.40, 0.34]\\n \\\"Campaign F\\\": [0.35, 0.78]\\n \\\"Our Target Product\\\": [0.5, 0.6]\",\n \"Requirement Analysis\": \"\",\n \"Requirement Pool\": [\n [\n \"P0\",\n \"The main code ...\"\n ],\n [\n \"P0\",\n \"The game algorithm ...\"\n ]\n ],\n \"UI Design draft\": \"Basic function description with a simple style and layout.\",\n \"Anything UNCLEAR\": \"\"\n}\n[/CONTENT]\n\n## nodes: \": # \"\n- Language: # Provide the language used in the project, typically matching the user's requirement language.\n- Programming Language: # Python/JavaScript or other mainstream programming language.\n- Original Requirements: # Place the original user's requirements here.\n- Product Goals: typing.List[str] # Provide up to three clear, orthogonal product goals.\n- User Stories: typing.List[str] # Provide up to 3 to 5 scenario-based user stories.\n- Competitive Analysis: typing.List[str] # Provide 5 to 7 competitive products.\n- Competitive Quadrant Chart: # Use mermaid quadrantChart syntax. Distribute scores evenly between 0 and 1\n- Requirement Analysis: # Provide a detailed analysis of the requirements.\n- Requirement Pool: typing.List[typing.List[str]] # List down the top-5 requirements with their priority (P0, P1, P2).\n- UI Design draft: # Provide a simple description of UI elements, functions, style, and layout.\n- Anything UNCLEAR: # Mention any aspects of the project that are unclear and try to clarify them.\n\n\n## constraint\nLanguage: Please use the same language as Human INPUT.\nFormat: output wrapped inside [CONTENT][/CONTENT] like format example, nothing else.\n\n## action\nFollow instructions of nodes, generate output and make sure it follows the format example.\n": "[CONTENT]\n{\n \"Language\": \"zh_cn\",\n \"Programming Language\": \"LLM\",\n \"Original Requirements\": \"需要一个基于LLM做总结的搜索引擎\",\n \"Product Goals\": [],\n \"User Stories\": [],\n \"Competitive Analysis\": [],\n \"Competitive Quadrant Chart\": \"\",\n \"Requirement Analysis\": \"\",\n \"Requirement Pool\": [],\n \"UI Design draft\": \"\",\n \"Anything UNCLEAR\": \"\"\n}\n[/CONTENT]",
+ "\n## context\n\n### Project Name\n20240111181214\n\n### Original Requirements\n['需要一个基于LLM做总结的搜索引擎']\n\n### Search Information\n-\n\n\n-----\n\n## format example\n[CONTENT]\n{\n \"Language\": \"en_us\",\n \"Programming Language\": \"Python\",\n \"Original Requirements\": \"Create a 2048 game\",\n \"Product Goals\": [\n \"Create an engaging user experience\",\n \"Improve accessibility, be responsive\",\n \"More beautiful UI\"\n ],\n \"User Stories\": [\n \"As a player, I want to be able to choose difficulty levels\",\n \"As a player, I want to see my score after each game\",\n \"As a player, I want to get restart button when I lose\",\n \"As a player, I want to see beautiful UI that make me feel good\",\n \"As a player, I want to play game via mobile phone\"\n ],\n \"Competitive Analysis\": [\n \"2048 Game A: Simple interface, lacks responsive features\",\n \"play2048.co: Beautiful and responsive UI with my best score shown\",\n \"2048game.com: Responsive UI with my best score shown, but many ads\"\n ],\n \"Competitive Quadrant Chart\": \"quadrantChart\\n title \\\"Reach and engagement of campaigns\\\"\\n x-axis \\\"Low Reach\\\" --> \\\"High Reach\\\"\\n y-axis \\\"Low Engagement\\\" --> \\\"High Engagement\\\"\\n quadrant-1 \\\"We should expand\\\"\\n quadrant-2 \\\"Need to promote\\\"\\n quadrant-3 \\\"Re-evaluate\\\"\\n quadrant-4 \\\"May be improved\\\"\\n \\\"Campaign A\\\": [0.3, 0.6]\\n \\\"Campaign B\\\": [0.45, 0.23]\\n \\\"Campaign C\\\": [0.57, 0.69]\\n \\\"Campaign D\\\": [0.78, 0.34]\\n \\\"Campaign E\\\": [0.40, 0.34]\\n \\\"Campaign F\\\": [0.35, 0.78]\\n \\\"Our Target Product\\\": [0.5, 0.6]\",\n \"Requirement Analysis\": \"\",\n \"Requirement Pool\": [\n [\n \"P0\",\n \"The main code ...\"\n ],\n [\n \"P0\",\n \"The game algorithm ...\"\n ]\n ],\n \"UI Design draft\": \"Basic function description with a simple style and layout.\",\n \"Anything UNCLEAR\": \"\"\n}\n[/CONTENT]\n\n## nodes: \": # \"\n- Language: # Provide the language used in the project, typically matching the user's requirement language.\n- Programming Language: # Python/JavaScript or other mainstream programming language.\n- Original Requirements: # Place the original user's requirements here.\n- Product Goals: typing.List[str] # Provide up to three clear, orthogonal product goals.\n- User Stories: typing.List[str] # Provide up to 3 to 5 scenario-based user stories.\n- Competitive Analysis: typing.List[str] # Provide 5 to 7 competitive products.\n- Competitive Quadrant Chart: # Use mermaid quadrantChart syntax. Distribute scores evenly between 0 and 1\n- Requirement Analysis: # Provide a detailed analysis of the requirements.\n- Requirement Pool: typing.List[typing.List[str]] # List down the top-5 requirements with their priority (P0, P1, P2).\n- UI Design draft: # Provide a simple description of UI elements, functions, style, and layout.\n- Anything UNCLEAR: # Mention any aspects of the project that are unclear and try to clarify them.\n\n\n## constraint\nLanguage: Please use the same language as Human INPUT.\nFormat: output wrapped inside [CONTENT][/CONTENT] like format example, nothing else.\n\n## action\nFollow instructions of nodes, generate output and make sure it follows the format example.\n": "[CONTENT]\n{\n \"Language\": \"zh_cn\",\n \"Programming Language\": \"LLM\",\n \"Original Requirements\": \"需要一个基于LLM做总结的搜索引擎\",\n \"Product Goals\": [],\n \"User Stories\": [],\n \"Competitive Analysis\": [],\n \"Competitive Quadrant Chart\": \"\",\n \"Requirement Analysis\": \"\",\n \"Requirement Pool\": [],\n \"UI Design draft\": \"\",\n \"Anything UNCLEAR\": \"\"\n}\n[/CONTENT]",
+ "\n## context\n\n### Project Name\n20240111181426\n\n### Original Requirements\n['开发一个基于大语言模型与私有知识库的搜索引擎,希望可以基于大语言模型进行搜索总结']\n\n### Search Information\n-\n\n\n-----\n\n## format example\n[CONTENT]\n{\n \"Language\": \"en_us\",\n \"Programming Language\": \"Python\",\n \"Original Requirements\": \"Create a 2048 game\",\n \"Product Goals\": [\n \"Create an engaging user experience\",\n \"Improve accessibility, be responsive\",\n \"More beautiful UI\"\n ],\n \"User Stories\": [\n \"As a player, I want to be able to choose difficulty levels\",\n \"As a player, I want to see my score after each game\",\n \"As a player, I want to get restart button when I lose\",\n \"As a player, I want to see beautiful UI that make me feel good\",\n \"As a player, I want to play game via mobile phone\"\n ],\n \"Competitive Analysis\": [\n \"2048 Game A: Simple interface, lacks responsive features\",\n \"play2048.co: Beautiful and responsive UI with my best score shown\",\n \"2048game.com: Responsive UI with my best score shown, but many ads\"\n ],\n \"Competitive Quadrant Chart\": \"quadrantChart\\n title \\\"Reach and engagement of campaigns\\\"\\n x-axis \\\"Low Reach\\\" --> \\\"High Reach\\\"\\n y-axis \\\"Low Engagement\\\" --> \\\"High Engagement\\\"\\n quadrant-1 \\\"We should expand\\\"\\n quadrant-2 \\\"Need to promote\\\"\\n quadrant-3 \\\"Re-evaluate\\\"\\n quadrant-4 \\\"May be improved\\\"\\n \\\"Campaign A\\\": [0.3, 0.6]\\n \\\"Campaign B\\\": [0.45, 0.23]\\n \\\"Campaign C\\\": [0.57, 0.69]\\n \\\"Campaign D\\\": [0.78, 0.34]\\n \\\"Campaign E\\\": [0.40, 0.34]\\n \\\"Campaign F\\\": [0.35, 0.78]\\n \\\"Our Target Product\\\": [0.5, 0.6]\",\n \"Requirement Analysis\": \"\",\n \"Requirement Pool\": [\n [\n \"P0\",\n \"The main code ...\"\n ],\n [\n \"P0\",\n \"The game algorithm ...\"\n ]\n ],\n \"UI Design draft\": \"Basic function description with a simple style and layout.\",\n \"Anything UNCLEAR\": \"\"\n}\n[/CONTENT]\n\n## nodes: \": # \"\n- Language: # Provide the language used in the project, typically matching the user's requirement language.\n- Programming Language: # Python/JavaScript or other mainstream programming language.\n- Original Requirements: # Place the original user's requirements here.\n- Product Goals: typing.List[str] # Provide up to three clear, orthogonal product goals.\n- User Stories: typing.List[str] # Provide up to 3 to 5 scenario-based user stories.\n- Competitive Analysis: typing.List[str] # Provide 5 to 7 competitive products.\n- Competitive Quadrant Chart: # Use mermaid quadrantChart syntax. Distribute scores evenly between 0 and 1\n- Requirement Analysis: # Provide a detailed analysis of the requirements.\n- Requirement Pool: typing.List[typing.List[str]] # List down the top-5 requirements with their priority (P0, P1, P2).\n- UI Design draft: # Provide a simple description of UI elements, functions, style, and layout.\n- Anything UNCLEAR: # Mention any aspects of the project that are unclear and try to clarify them.\n\n\n## constraint\nLanguage: Please use the same language as Human INPUT.\nFormat: output wrapped inside [CONTENT][/CONTENT] like format example, nothing else.\n\n## action\nFollow instructions of nodes, generate output and make sure it follows the format example.\n": "[CONTENT]\n{\n \"Language\": \"zh_cn\",\n \"Programming Language\": \"Python\",\n \"Original Requirements\": \"开发一个基于大语言模型与私有知识库的搜索引擎,希望可以基于大语言模型进行搜索总结\",\n \"Product Goals\": [\n \"提供高效的搜索功能\",\n \"整合私有知识库\",\n \"生成准确的搜索总结\"\n ],\n \"User Stories\": [\n \"作为用户,我希望能够快速找到所需信息\",\n \"作为用户,我希望搜索结果能够涵盖私有知识库内容\",\n \"作为用户,我希望搜索总结能够准确反映所需信息\"\n ],\n \"Competitive Analysis\": [\n \"搜索引擎A:搜索速度快,但不支持私有知识库整合\",\n \"搜索引擎B:支持私有知识库整合,但搜索总结不够准确\",\n \"搜索引擎C:准确的搜索总结,但不支持私有知识库整合\"\n ],\n \"Competitive Quadrant Chart\": \"quadrantChart\\n title \\\"搜索引擎比较\\\"\\n x-axis \\\"低速度\\\" --> \\\"高速度\\\"\\n y-axis \\\"低准确性\\\" --> \\\"高准确性\\\"\\n quadrant-1 \\\"速度快,准确性低\\\"\\n quadrant-2 \\\"速度慢,准确性低\\\"\\n quadrant-3 \\\"速度慢,准确性高\\\"\\n quadrant-4 \\\"速度快,准确性高\\\"\\n \\\"搜索引擎A\\\": [0.8, 0.3]\\n \\\"搜索引擎B\\\": [0.4, 0.2]\\n \\\"搜索引擎C\\\": [0.2, 0.9]\\n \\\"我们的目标产品\\\": [0.7, 0.8]\",\n \"Requirement Analysis\": \"\",\n \"Requirement Pool\": [\n [\n \"P0\",\n \"基于大语言模型的搜索功能\"\n ],\n [\n \"P0\",\n \"私有知识库整合\"\n ],\n [\n \"P1\",\n \"搜索总结生成\"\n ]\n ],\n \"UI Design draft\": \"简洁的搜索界面,包含私有知识库搜索选项。\",\n \"Anything UNCLEAR\": \"\"\n}\n[/CONTENT]",
+ "\n## context\n\n### Project Name\n20240111181426\n\n### Original Requirements\n['']\n\n### Search Information\n-\n\n\n-----\n\n## format example\n[CONTENT]\n{\n \"Language\": \"en_us\",\n \"Programming Language\": \"Python\",\n \"Original Requirements\": \"Create a 2048 game\",\n \"Product Goals\": [\n \"Create an engaging user experience\",\n \"Improve accessibility, be responsive\",\n \"More beautiful UI\"\n ],\n \"User Stories\": [\n \"As a player, I want to be able to choose difficulty levels\",\n \"As a player, I want to see my score after each game\",\n \"As a player, I want to get restart button when I lose\",\n \"As a player, I want to see beautiful UI that make me feel good\",\n \"As a player, I want to play game via mobile phone\"\n ],\n \"Competitive Analysis\": [\n \"2048 Game A: Simple interface, lacks responsive features\",\n \"play2048.co: Beautiful and responsive UI with my best score shown\",\n \"2048game.com: Responsive UI with my best score shown, but many ads\"\n ],\n \"Competitive Quadrant Chart\": \"quadrantChart\\n title \\\"Reach and engagement of campaigns\\\"\\n x-axis \\\"Low Reach\\\" --> \\\"High Reach\\\"\\n y-axis \\\"Low Engagement\\\" --> \\\"High Engagement\\\"\\n quadrant-1 \\\"We should expand\\\"\\n quadrant-2 \\\"Need to promote\\\"\\n quadrant-3 \\\"Re-evaluate\\\"\\n quadrant-4 \\\"May be improved\\\"\\n \\\"Campaign A\\\": [0.3, 0.6]\\n \\\"Campaign B\\\": [0.45, 0.23]\\n \\\"Campaign C\\\": [0.57, 0.69]\\n \\\"Campaign D\\\": [0.78, 0.34]\\n \\\"Campaign E\\\": [0.40, 0.34]\\n \\\"Campaign F\\\": [0.35, 0.78]\\n \\\"Our Target Product\\\": [0.5, 0.6]\",\n \"Requirement Analysis\": \"\",\n \"Requirement Pool\": [\n [\n \"P0\",\n \"The main code ...\"\n ],\n [\n \"P0\",\n \"The game algorithm ...\"\n ]\n ],\n \"UI Design draft\": \"Basic function description with a simple style and layout.\",\n \"Anything UNCLEAR\": \"\"\n}\n[/CONTENT]\n\n## nodes: \": # \"\n- Language: # Provide the language used in the project, typically matching the user's requirement language.\n- Programming Language: # Python/JavaScript or other mainstream programming language.\n- Original Requirements: # Place the original user's requirements here.\n- Product Goals: typing.List[str] # Provide up to three clear, orthogonal product goals.\n- User Stories: typing.List[str] # Provide up to 3 to 5 scenario-based user stories.\n- Competitive Analysis: typing.List[str] # Provide 5 to 7 competitive products.\n- Competitive Quadrant Chart: # Use mermaid quadrantChart syntax. Distribute scores evenly between 0 and 1\n- Requirement Analysis: # Provide a detailed analysis of the requirements.\n- Requirement Pool: typing.List[typing.List[str]] # List down the top-5 requirements with their priority (P0, P1, P2).\n- UI Design draft: # Provide a simple description of UI elements, functions, style, and layout.\n- Anything UNCLEAR: # Mention any aspects of the project that are unclear and try to clarify them.\n\n\n## constraint\nLanguage: Please use the same language as Human INPUT.\nFormat: output wrapped inside [CONTENT][/CONTENT] like format example, nothing else.\n\n## action\nFollow instructions of nodes, generate output and make sure it follows the format example.\n": "[CONTENT]\n{\n \"Language\": \"en_us\",\n \"Programming Language\": \"Python\",\n \"Original Requirements\": \"\",\n \"Product Goals\": [],\n \"User Stories\": [],\n \"Competitive Analysis\": [],\n \"Competitive Quadrant Chart\": \"quadrantChart\\n title \\\"Reach and engagement of campaigns\\\"\\n x-axis \\\"Low Reach\\\" --> \\\"High Reach\\\"\\n y-axis \\\"Low Engagement\\\" --> \\\"High Engagement\\\"\\n quadrant-1 \\\"We should expand\\\"\\n quadrant-2 \\\"Need to promote\\\"\\n quadrant-3 \\\"Re-evaluate\\\"\\n quadrant-4 \\\"May be improved\\\"\\n \\\"Campaign A\\\": [0.3, 0.6]\\n \\\"Campaign B\\\": [0.45, 0.23]\\n \\\"Campaign C\\\": [0.57, 0.69]\\n \\\"Campaign D\\\": [0.78, 0.34]\\n \\\"Campaign E\\\": [0.40, 0.34]\\n \\\"Campaign F\\\": [0.35, 0.78]\\n \\\"Our Target Product\\\": [0.5, 0.6]\",\n \"Requirement Analysis\": \"\",\n \"Requirement Pool\": [],\n \"UI Design draft\": \"Basic function description with a simple style and layout.\",\n \"Anything UNCLEAR\": \"\"\n}\n[/CONTENT]",
+ "## History Messages\n0: Human: Topic: climate change. Under 80 words per message.\n\n## Actions\nLanguage: Please use the same language as Human INPUT.\nSay your opinion with emotion and don't repeat it": "I believe that climate change is a critical issue that requires urgent action. It's alarming to see the impact of human activities on the environment and the devastating consequences it has on ecosystems and communities. We need to prioritize sustainable practices and reduce our carbon footprint to mitigate the effects of climate change. It's essential for the well-being of future generations and the health of our planet.",
+ "## History Messages\n0: Alex(Democratic candidate): I believe that climate change is a critical issue that requires urgent action. It's alarming to see the impact of human activities on the environment and the devastating consequences it has on ecosystems and communities. We need to prioritize sustainable practices and reduce our carbon footprint to mitigate the effects of climate change. It's essential for the well-being of future generations and the health of our planet.\n\n## Actions\nLanguage: Please use the same language as Human INPUT.\nSay your opinion with emotion and don't repeat it": "I completely agree with Alex. Climate change is a critical issue that demands immediate attention. The impact of human activities on the environment is devastating, and urgent action is necessary to prioritize sustainable practices and reduce our carbon footprint. It's essential for the well-being of future generations and the health of our planet.",
+ "## History Messages\n0: Bob(Republican candidate): I completely agree with Alex. Climate change is a critical issue that demands immediate attention. The impact of human activities on the environment is devastating, and urgent action is necessary to prioritize sustainable practices and reduce our carbon footprint. It's essential for the well-being of future generations and the health of our planet.\n1: Alex(Democratic candidate): I believe that climate change is a critical issue that requires urgent action. It's alarming to see the impact of human activities on the environment and the devastating consequences it has on ecosystems and communities. We need to prioritize sustainable practices and reduce our carbon footprint to mitigate the effects of climate change. It's essential for the well-being of future generations and the health of our planet.\n2: Human: Topic: climate change. Under 80 words per message.\n\n## Actions\nLanguage: Please use the same language as Human INPUT.\nSay your opinion with emotion and don't repeat it": "I completely agree with both Bob and Alex. Climate change is a critical issue that demands immediate attention. The impact of human activities on the environment is devastating, and urgent action is necessary to prioritize sustainable practices and reduce our carbon footprint. It's essential for the well-being of future generations and the health of our planet.",
+ "\n## context\n\n### Project Name\n20240112110621\n\n### Original Requirements\n['需要一个基于LLM做总结的搜索引擎']\n\n### Search Information\n-\n\n\n-----\n\n## format example\n[CONTENT]\n{\n \"Language\": \"en_us\",\n \"Programming Language\": \"Python\",\n \"Original Requirements\": \"Create a 2048 game\",\n \"Product Goals\": [\n \"Create an engaging user experience\",\n \"Improve accessibility, be responsive\",\n \"More beautiful UI\"\n ],\n \"User Stories\": [\n \"As a player, I want to be able to choose difficulty levels\",\n \"As a player, I want to see my score after each game\",\n \"As a player, I want to get restart button when I lose\",\n \"As a player, I want to see beautiful UI that make me feel good\",\n \"As a player, I want to play game via mobile phone\"\n ],\n \"Competitive Analysis\": [\n \"2048 Game A: Simple interface, lacks responsive features\",\n \"play2048.co: Beautiful and responsive UI with my best score shown\",\n \"2048game.com: Responsive UI with my best score shown, but many ads\"\n ],\n \"Competitive Quadrant Chart\": \"quadrantChart\\n title \\\"Reach and engagement of campaigns\\\"\\n x-axis \\\"Low Reach\\\" --> \\\"High Reach\\\"\\n y-axis \\\"Low Engagement\\\" --> \\\"High Engagement\\\"\\n quadrant-1 \\\"We should expand\\\"\\n quadrant-2 \\\"Need to promote\\\"\\n quadrant-3 \\\"Re-evaluate\\\"\\n quadrant-4 \\\"May be improved\\\"\\n \\\"Campaign A\\\": [0.3, 0.6]\\n \\\"Campaign B\\\": [0.45, 0.23]\\n \\\"Campaign C\\\": [0.57, 0.69]\\n \\\"Campaign D\\\": [0.78, 0.34]\\n \\\"Campaign E\\\": [0.40, 0.34]\\n \\\"Campaign F\\\": [0.35, 0.78]\\n \\\"Our Target Product\\\": [0.5, 0.6]\",\n \"Requirement Analysis\": \"\",\n \"Requirement Pool\": [\n [\n \"P0\",\n \"The main code ...\"\n ],\n [\n \"P0\",\n \"The game algorithm ...\"\n ]\n ],\n \"UI Design draft\": \"Basic function description with a simple style and layout.\",\n \"Anything UNCLEAR\": \"\"\n}\n[/CONTENT]\n\n## nodes: \": # \"\n- Language: # Provide the language used in the project, typically matching the user's requirement language.\n- Programming Language: # Python/JavaScript or other mainstream programming language.\n- Original Requirements: # Place the original user's requirements here.\n- Product Goals: typing.List[str] # Provide up to three clear, orthogonal product goals.\n- User Stories: typing.List[str] # Provide up to 3 to 5 scenario-based user stories.\n- Competitive Analysis: typing.List[str] # Provide 5 to 7 competitive products.\n- Competitive Quadrant Chart: # Use mermaid quadrantChart syntax. Distribute scores evenly between 0 and 1\n- Requirement Analysis: # Provide a detailed analysis of the requirements.\n- Requirement Pool: typing.List[typing.List[str]] # List down the top-5 requirements with their priority (P0, P1, P2).\n- UI Design draft: # Provide a simple description of UI elements, functions, style, and layout.\n- Anything UNCLEAR: # Mention any aspects of the project that are unclear and try to clarify them.\n\n\n## constraint\nLanguage: Please use the same language as Human INPUT.\nFormat: output wrapped inside [CONTENT][/CONTENT] like format example, nothing else.\n\n## action\nFollow instructions of nodes, generate output and make sure it follows the format example.\n": "[CONTENT]\n{\n \"Language\": \"zh_cn\",\n \"Programming Language\": \"LLM\",\n \"Original Requirements\": \"需要一个基于LLM做总结的搜索引擎\",\n \"Product Goals\": [],\n \"User Stories\": [],\n \"Competitive Analysis\": [],\n \"Competitive Quadrant Chart\": \"\",\n \"Requirement Analysis\": \"\",\n \"Requirement Pool\": [],\n \"UI Design draft\": \"\",\n \"Anything UNCLEAR\": \"\"\n}\n[/CONTENT]",
+ "You are a python code to Mermaid Sequence Diagram translator in function detail#SYSTEM_MSG_END#```python\n#!/usr/bin/env python\n# -*- coding: utf-8 -*-\nimport asyncio\nimport shutil\nfrom pathlib import Path\n\nimport typer\n\nfrom metagpt.config2 import config\nfrom metagpt.const import CONFIG_ROOT, METAGPT_ROOT\n\napp = typer.Typer(add_completion=False, pretty_exceptions_show_locals=False)\n\n\ndef generate_repo(\n idea,\n investment,\n n_round,\n code_review,\n run_tests,\n implement,\n project_name,\n inc,\n project_path,\n reqa_file,\n max_auto_summarize_code,\n recover_path,\n):\n \"\"\"Run the startup logic. Can be called from CLI or other Python scripts.\"\"\"\n from metagpt.roles import (\n Architect,\n Engineer,\n ProductManager,\n ProjectManager,\n QaEngineer,\n )\n from metagpt.team import Team\n\n config.update_via_cli(project_path, project_name, inc, reqa_file, max_auto_summarize_code)\n\n if not recover_path:\n company = Team()\n company.hire(\n [\n ProductManager(),\n Architect(),\n ProjectManager(),\n ]\n )\n\n if implement or code_review:\n company.hire([Engineer(n_borg=5, use_code_review=code_review)])\n\n if run_tests:\n company.hire([QaEngineer()])\n else:\n stg_path = Path(recover_path)\n if not stg_path.exists() or not str(stg_path).endswith(\"team\"):\n raise FileNotFoundError(f\"{recover_path} not exists or not endswith `team`\")\n\n company = Team.deserialize(stg_path=stg_path)\n idea = company.idea\n\n company.invest(investment)\n company.run_project(idea)\n asyncio.run(company.run(n_round=n_round))\n\n\n@app.command(\"\", help=\"Start a new project.\")\ndef startup(\n idea: str = typer.Argument(None, help=\"Your innovative idea, such as 'Create a 2048 game.'\"),\n investment: float = typer.Option(default=3.0, help=\"Dollar amount to invest in the AI company.\"),\n n_round: int = typer.Option(default=5, help=\"Number of rounds for the simulation.\"),\n code_review: bool = typer.Option(default=True, help=\"Whether to use code review.\"),\n run_tests: bool = typer.Option(default=False, help=\"Whether to enable QA for adding & running tests.\"),\n implement: bool = typer.Option(default=True, help=\"Enable or disable code implementation.\"),\n project_name: str = typer.Option(default=\"\", help=\"Unique project name, such as 'game_2048'.\"),\n inc: bool = typer.Option(default=False, help=\"Incremental mode. Use it to coop with existing repo.\"),\n project_path: str = typer.Option(\n default=\"\",\n help=\"Specify the directory path of the old version project to fulfill the incremental requirements.\",\n ),\n reqa_file: str = typer.Option(\n default=\"\", help=\"Specify the source file name for rewriting the quality assurance code.\"\n ),\n max_auto_summarize_code: int = typer.Option(\n default=0,\n help=\"The maximum number of times the 'SummarizeCode' action is automatically invoked, with -1 indicating \"\n \"unlimited. This parameter is used for debugging the workflow.\",\n ),\n recover_path: str = typer.Option(default=None, help=\"recover the project from existing serialized storage\"),\n init_config: bool = typer.Option(default=False, help=\"Initialize the configuration file for MetaGPT.\"),\n):\n \"\"\"Run a startup. Be a boss.\"\"\"\n if init_config:\n copy_config_to()\n return\n\n if idea is None:\n typer.echo(\"Missing argument 'IDEA'. Run 'metagpt --help' for more information.\")\n raise typer.Exit()\n\n return generate_repo(\n idea,\n investment,\n n_round,\n code_review,\n run_tests,\n implement,\n project_name,\n inc,\n project_path,\n reqa_file,\n max_auto_summarize_code,\n recover_path,\n )\n\n\ndef copy_config_to(config_path=METAGPT_ROOT / \"config\" / \"config2.yaml\"):\n \"\"\"Initialize the configuration file for MetaGPT.\"\"\"\n target_path = CONFIG_ROOT / \"config2.yaml\"\n\n # 创建目标目录(如果不存在)\n target_path.parent.mkdir(parents=True, exist_ok=True)\n\n # 如果目标文件已经存在,则重命名为 .bak\n if target_path.exists():\n backup_path = target_path.with_suffix(\".bak\")\n target_path.rename(backup_path)\n print(f\"Existing configuration file backed up at {backup_path}\")\n\n # 复制文件\n shutil.copy(str(config_path), target_path)\n print(f\"Configuration file initialized at {target_path}\")\n\n\nif __name__ == \"__main__\":\n app()\n\n```\n\n---\nTranslate the code above into Mermaid Sequence Diagram.": "```mermaid\nsequenceDiagram\n participant app\n participant generate_repo\n participant copy_config_to\n participant Team\n participant ProductManager\n participant Architect\n participant ProjectManager\n participant Engineer\n participant QaEngineer\n\n app -> generate_repo: startup()\n generate_repo -> config: update_via_cli()\n generate_repo -> Team: hire()\n Team -> ProductManager: hire()\n Team -> Architect: hire()\n Team -> ProjectManager: hire()\n generate_repo -> Engineer: hire()\n generate_repo -> QaEngineer: hire()\n generate_repo -> Team: invest()\n generate_repo -> Team: run_project()\n generate_repo -> Team: run()\n\n app -> copy_config_to: copy_config_to()\n copy_config_to -> config: update_via_cli()\n```",
+ "You are a python code to Mermaid Sequence Diagram translator in function detail#SYSTEM_MSG_END#```python\n#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\"\"\"\n@Time : 2023/12/14 11:40\n@Author : alexanderwu\n@File : write_prd_an.py\n\"\"\"\nfrom typing import List\n\nfrom metagpt.actions.action_node import ActionNode\n\nLANGUAGE = ActionNode(\n key=\"Language\",\n expected_type=str,\n instruction=\"Provide the language used in the project, typically matching the user's requirement language.\",\n example=\"en_us\",\n)\n\nPROGRAMMING_LANGUAGE = ActionNode(\n key=\"Programming Language\",\n expected_type=str,\n instruction=\"Python/JavaScript or other mainstream programming language.\",\n example=\"Python\",\n)\n\nORIGINAL_REQUIREMENTS = ActionNode(\n key=\"Original Requirements\",\n expected_type=str,\n instruction=\"Place the original user's requirements here.\",\n example=\"Create a 2048 game\",\n)\n\nPROJECT_NAME = ActionNode(\n key=\"Project Name\",\n expected_type=str,\n instruction='According to the content of \"Original Requirements,\" name the project using snake case style , '\n \"like 'game_2048' or 'simple_crm.\",\n example=\"game_2048\",\n)\n\nPRODUCT_GOALS = ActionNode(\n key=\"Product Goals\",\n expected_type=List[str],\n instruction=\"Provide up to three clear, orthogonal product goals.\",\n example=[\"Create an engaging user experience\", \"Improve accessibility, be responsive\", \"More beautiful UI\"],\n)\n\nUSER_STORIES = ActionNode(\n key=\"User Stories\",\n expected_type=List[str],\n instruction=\"Provide up to 3 to 5 scenario-based user stories.\",\n example=[\n \"As a player, I want to be able to choose difficulty levels\",\n \"As a player, I want to see my score after each game\",\n \"As a player, I want to get restart button when I lose\",\n \"As a player, I want to see beautiful UI that make me feel good\",\n \"As a player, I want to play game via mobile phone\",\n ],\n)\n\nCOMPETITIVE_ANALYSIS = ActionNode(\n key=\"Competitive Analysis\",\n expected_type=List[str],\n instruction=\"Provide 5 to 7 competitive products.\",\n example=[\n \"2048 Game A: Simple interface, lacks responsive features\",\n \"play2048.co: Beautiful and responsive UI with my best score shown\",\n \"2048game.com: Responsive UI with my best score shown, but many ads\",\n ],\n)\n\nCOMPETITIVE_QUADRANT_CHART = ActionNode(\n key=\"Competitive Quadrant Chart\",\n expected_type=str,\n instruction=\"Use mermaid quadrantChart syntax. Distribute scores evenly between 0 and 1\",\n example=\"\"\"quadrantChart\n title \"Reach and engagement of campaigns\"\n x-axis \"Low Reach\" --> \"High Reach\"\n y-axis \"Low Engagement\" --> \"High Engagement\"\n quadrant-1 \"We should expand\"\n quadrant-2 \"Need to promote\"\n quadrant-3 \"Re-evaluate\"\n quadrant-4 \"May be improved\"\n \"Campaign A\": [0.3, 0.6]\n \"Campaign B\": [0.45, 0.23]\n \"Campaign C\": [0.57, 0.69]\n \"Campaign D\": [0.78, 0.34]\n \"Campaign E\": [0.40, 0.34]\n \"Campaign F\": [0.35, 0.78]\n \"Our Target Product\": [0.5, 0.6]\"\"\",\n)\n\nREQUIREMENT_ANALYSIS = ActionNode(\n key=\"Requirement Analysis\",\n expected_type=str,\n instruction=\"Provide a detailed analysis of the requirements.\",\n example=\"\",\n)\n\nREQUIREMENT_POOL = ActionNode(\n key=\"Requirement Pool\",\n expected_type=List[List[str]],\n instruction=\"List down the top-5 requirements with their priority (P0, P1, P2).\",\n example=[[\"P0\", \"The main code ...\"], [\"P0\", \"The game algorithm ...\"]],\n)\n\nUI_DESIGN_DRAFT = ActionNode(\n key=\"UI Design draft\",\n expected_type=str,\n instruction=\"Provide a simple description of UI elements, functions, style, and layout.\",\n example=\"Basic function description with a simple style and layout.\",\n)\n\nANYTHING_UNCLEAR = ActionNode(\n key=\"Anything UNCLEAR\",\n expected_type=str,\n instruction=\"Mention any aspects of the project that are unclear and try to clarify them.\",\n example=\"\",\n)\n\nISSUE_TYPE = ActionNode(\n key=\"issue_type\",\n expected_type=str,\n instruction=\"Answer BUG/REQUIREMENT. If it is a bugfix, answer BUG, otherwise answer Requirement\",\n example=\"BUG\",\n)\n\nIS_RELATIVE = ActionNode(\n key=\"is_relative\",\n expected_type=str,\n instruction=\"Answer YES/NO. If the requirement is related to the old PRD, answer YES, otherwise NO\",\n example=\"YES\",\n)\n\nREASON = ActionNode(\n key=\"reason\", expected_type=str, instruction=\"Explain the reasoning process from question to answer\", example=\"...\"\n)\n\n\nNODES = [\n LANGUAGE,\n PROGRAMMING_LANGUAGE,\n ORIGINAL_REQUIREMENTS,\n PROJECT_NAME,\n PRODUCT_GOALS,\n USER_STORIES,\n COMPETITIVE_ANALYSIS,\n COMPETITIVE_QUADRANT_CHART,\n REQUIREMENT_ANALYSIS,\n REQUIREMENT_POOL,\n UI_DESIGN_DRAFT,\n ANYTHING_UNCLEAR,\n]\n\nWRITE_PRD_NODE = ActionNode.from_children(\"WritePRD\", NODES)\nWP_ISSUE_TYPE_NODE = ActionNode.from_children(\"WP_ISSUE_TYPE\", [ISSUE_TYPE, REASON])\nWP_IS_RELATIVE_NODE = ActionNode.from_children(\"WP_IS_RELATIVE\", [IS_RELATIVE, REASON])\n\n```\n\n---\nTranslate the code above into Mermaid Sequence Diagram.": "```mermaid\nclassDef actionNode fill:#f9f,stroke:#333,stroke-width:2px;\nclassDef actionNodeTitle fill:#f9f,stroke:#333,stroke-width:2px,font-weight:bold;\nclassDef actionNodeExample fill:#f9f,stroke:#333,stroke-width:2px,font-style:italic;\n\nclass ActionNodeTitle actionNodeTitle\nclass ActionNodeExample actionNodeExample\n\nActionNodeTitle:::Language --> \"Language\"\nActionNodeExample:::Language --> \"Provide the language used in the project, typically matching the user's requirement language.\\nExample: en_us\"\n\nActionNodeTitle:::ProgrammingLanguage --> \"Programming Language\"\nActionNodeExample:::ProgrammingLanguage --> \"Python/JavaScript or other mainstream programming language.\\nExample: Python\"\n\nActionNodeTitle:::OriginalRequirements --> \"Original Requirements\"\nActionNodeExample:::OriginalRequirements --> \"Place the original user's requirements here.\\nExample: Create a 2048 game\"\n\nActionNodeTitle:::ProjectName --> \"Project Name\"\nActionNodeExample:::ProjectName --> 'According to the content of \"Original Requirements,\" name the project using snake case style , like \\'game_2048\\' or \\'simple_crm.\\nExample: game_2048'\n\nActionNodeTitle:::ProductGoals --> \"Product Goals\"\nActionNodeExample:::ProductGoals --> \"Provide up to three clear, orthogonal product goals.\\nExample:\\n- Create an engaging user experience\\n- Improve accessibility, be responsive\\n- More beautiful UI\"\n\nActionNodeTitle:::UserStories --> \"User Stories\"\nActionNodeExample:::UserStories --> \"Provide up to 3 to 5 scenario-based user stories.\\nExample:\\n- As a player, I want to be able to choose difficulty levels\\n- As a player, I want to see my score after each game\\n- As a player, I want to get restart button when I lose\\n- As a player, I want to see beautiful UI that make me feel good\\n- As a player, I want to play game via mobile phone\"\n\nActionNodeTitle:::CompetitiveAnalysis --> \"Competitive Analysis\"\nActionNodeExample:::CompetitiveAnalysis --> \"Provide 5 to 7 competitive products.\\nExample:\\n- 2048 Game A: Simple interface, lacks responsive features\\n- play2048.co: Beautiful and responsive UI with my best score shown\\n- 2048game.com: Responsive UI with my best score shown, but many ads\"\n\nActionNodeTitle:::CompetitiveQuadrantChart --> \"Competitive Quadrant Chart\"\nActionNodeExample:::CompetitiveQuadrantChart --> \"Use mermaid quadrantChart syntax. Distribute scores evenly between 0 and 1\\nExample:\\nquadrantChart\\n title \\\"Reach and engagement of campaigns\\\"\\n x-axis \\\"Low Reach\\\" --> \\\"High Reach\\\"\\n y-axis \\\"Low Engagement\\\" --> \\\"High Engagement\\\"\\n quadrant-1 \\\"We should expand\\\"\\n quadrant-2 \\\"Need to promote\\\"\\n quadrant-3 \\\"Re-evaluate\\\"\\n quadrant-4 \\\"May be improved\\\"\\n \\\"Campaign A\\\": [0.3, 0.6]\\n \\\"Campaign B\\\": [0.45, 0.23]\\n \\\"Campaign C\\\": [0.57, 0.69]\\n \\\"Campaign D\\\": [0.78, 0.34]\\n \\\"Campaign E\\\": [0.40, 0.34]\\n \\\"Campaign F\\\": [0.35, 0.78]\\n \\\"Our Target Product\\\": [0.5, 0.6]\"\n\nActionNodeTitle:::RequirementAnalysis --> \"Requirement Analysis\"\nActionNodeExample:::RequirementAnalysis --> \"Provide a detailed analysis of the requirements.\\nExample: \"\n\nActionNodeTitle:::RequirementPool --> \"Requirement Pool\"\nActionNodeExample:::RequirementPool --> \"List down the top-5 requirements with their priority (P0, P1, P2).\\nExample:\\n- P0: The main code ...\\n- P0: The game algorithm ...\"\n\nActionNodeTitle:::UIDesignDraft --> \"UI Design draft\"\nActionNodeExample:::UIDesignDraft --> \"Provide a simple description of UI elements, functions, style, and layout.\\nExample: Basic function description with a simple style and layout.\"\n\nActionNodeTitle:::AnythingUNCLEAR --> \"Anything UNCLEAR\"\nActionNodeExample:::AnythingUNCLEAR --> \"Mention any aspects of the project that are unclear and try to clarify them.\\nExample: \"\n\nActionNodeTitle:::issue_type --> \"issue_type\"\nActionNodeExample:::issue_type --> \"Answer BUG/REQUIREMENT. If it is a bugfix, answer BUG, otherwise answer Requirement\\nExample: BUG\"\n\nActionNodeTitle:::is_relative --> \"is_relative\"\nActionNodeExample:::is_relative --> \"Answer YES/NO. If the requirement is related to the old PRD, answer YES, otherwise NO\\nExample: YES\"\n\nActionNodeTitle:::reason --> \"reason\"\nActionNodeExample:::reason --> \"Explain the reasoning process from question to answer\\nExample: ...\"\n\nActionNodeTitle:::WritePRD --> \"WritePRD\"\nActionNodeExample:::WritePRD --> \"Language\\nProgramming Language\\nOriginal Requirements\\nProject Name\\nProduct Goals\\nUser Stories\\nCompetitive Analysis\\nCompetitive Quadrant Chart\\nRequirement Analysis\\nRequirement Pool\\nUI Design draft\\nAnything UNCLEAR\"\n\nActionNodeTitle:::WP_ISSUE_TYPE --> \"WP_ISSUE_TYPE\"\nActionNodeExample:::WP_ISSUE_TYPE --> \"issue_type\\nreason\"\n\nActionNodeTitle:::WP_IS_RELATIVE --> \"WP_IS_RELATIVE\"\nActionNodeExample:::WP_IS_RELATIVE --> \"is_relative\\nreason\"\n```",
+ "\n## context\n\n### Project Name\n20240112110833\n\n### Original Requirements\n['开发一个基于大语言模型与私有知识库的搜索引擎,希望可以基于大语言模型进行搜索总结']\n\n### Search Information\n-\n\n\n-----\n\n## format example\n[CONTENT]\n{\n \"Language\": \"en_us\",\n \"Programming Language\": \"Python\",\n \"Original Requirements\": \"Create a 2048 game\",\n \"Product Goals\": [\n \"Create an engaging user experience\",\n \"Improve accessibility, be responsive\",\n \"More beautiful UI\"\n ],\n \"User Stories\": [\n \"As a player, I want to be able to choose difficulty levels\",\n \"As a player, I want to see my score after each game\",\n \"As a player, I want to get restart button when I lose\",\n \"As a player, I want to see beautiful UI that make me feel good\",\n \"As a player, I want to play game via mobile phone\"\n ],\n \"Competitive Analysis\": [\n \"2048 Game A: Simple interface, lacks responsive features\",\n \"play2048.co: Beautiful and responsive UI with my best score shown\",\n \"2048game.com: Responsive UI with my best score shown, but many ads\"\n ],\n \"Competitive Quadrant Chart\": \"quadrantChart\\n title \\\"Reach and engagement of campaigns\\\"\\n x-axis \\\"Low Reach\\\" --> \\\"High Reach\\\"\\n y-axis \\\"Low Engagement\\\" --> \\\"High Engagement\\\"\\n quadrant-1 \\\"We should expand\\\"\\n quadrant-2 \\\"Need to promote\\\"\\n quadrant-3 \\\"Re-evaluate\\\"\\n quadrant-4 \\\"May be improved\\\"\\n \\\"Campaign A\\\": [0.3, 0.6]\\n \\\"Campaign B\\\": [0.45, 0.23]\\n \\\"Campaign C\\\": [0.57, 0.69]\\n \\\"Campaign D\\\": [0.78, 0.34]\\n \\\"Campaign E\\\": [0.40, 0.34]\\n \\\"Campaign F\\\": [0.35, 0.78]\\n \\\"Our Target Product\\\": [0.5, 0.6]\",\n \"Requirement Analysis\": \"\",\n \"Requirement Pool\": [\n [\n \"P0\",\n \"The main code ...\"\n ],\n [\n \"P0\",\n \"The game algorithm ...\"\n ]\n ],\n \"UI Design draft\": \"Basic function description with a simple style and layout.\",\n \"Anything UNCLEAR\": \"\"\n}\n[/CONTENT]\n\n## nodes: \": # \"\n- Language: # Provide the language used in the project, typically matching the user's requirement language.\n- Programming Language: # Python/JavaScript or other mainstream programming language.\n- Original Requirements: # Place the original user's requirements here.\n- Product Goals: typing.List[str] # Provide up to three clear, orthogonal product goals.\n- User Stories: typing.List[str] # Provide up to 3 to 5 scenario-based user stories.\n- Competitive Analysis: typing.List[str] # Provide 5 to 7 competitive products.\n- Competitive Quadrant Chart: # Use mermaid quadrantChart syntax. Distribute scores evenly between 0 and 1\n- Requirement Analysis: # Provide a detailed analysis of the requirements.\n- Requirement Pool: typing.List[typing.List[str]] # List down the top-5 requirements with their priority (P0, P1, P2).\n- UI Design draft: # Provide a simple description of UI elements, functions, style, and layout.\n- Anything UNCLEAR: # Mention any aspects of the project that are unclear and try to clarify them.\n\n\n## constraint\nLanguage: Please use the same language as Human INPUT.\nFormat: output wrapped inside [CONTENT][/CONTENT] like format example, nothing else.\n\n## action\nFollow instructions of nodes, generate output and make sure it follows the format example.\n": "[CONTENT]\n{\n \"Language\": \"zh_cn\",\n \"Programming Language\": \"Python\",\n \"Original Requirements\": \"开发一个基于大语言模型与私有知识库的搜索引擎,希望可以基于大语言模型进行搜索总结\",\n \"Product Goals\": [],\n \"User Stories\": [],\n \"Competitive Analysis\": [],\n \"Competitive Quadrant Chart\": \"\",\n \"Requirement Analysis\": \"\",\n \"Requirement Pool\": [],\n \"UI Design draft\": \"\",\n \"Anything UNCLEAR\": \"\"\n}\n[/CONTENT]",
+ "\n## context\n\n### Project Name\n20240112110833\n\n### Original Requirements\n['']\n\n### Search Information\n-\n\n\n-----\n\n## format example\n[CONTENT]\n{\n \"Language\": \"en_us\",\n \"Programming Language\": \"Python\",\n \"Original Requirements\": \"Create a 2048 game\",\n \"Product Goals\": [\n \"Create an engaging user experience\",\n \"Improve accessibility, be responsive\",\n \"More beautiful UI\"\n ],\n \"User Stories\": [\n \"As a player, I want to be able to choose difficulty levels\",\n \"As a player, I want to see my score after each game\",\n \"As a player, I want to get restart button when I lose\",\n \"As a player, I want to see beautiful UI that make me feel good\",\n \"As a player, I want to play game via mobile phone\"\n ],\n \"Competitive Analysis\": [\n \"2048 Game A: Simple interface, lacks responsive features\",\n \"play2048.co: Beautiful and responsive UI with my best score shown\",\n \"2048game.com: Responsive UI with my best score shown, but many ads\"\n ],\n \"Competitive Quadrant Chart\": \"quadrantChart\\n title \\\"Reach and engagement of campaigns\\\"\\n x-axis \\\"Low Reach\\\" --> \\\"High Reach\\\"\\n y-axis \\\"Low Engagement\\\" --> \\\"High Engagement\\\"\\n quadrant-1 \\\"We should expand\\\"\\n quadrant-2 \\\"Need to promote\\\"\\n quadrant-3 \\\"Re-evaluate\\\"\\n quadrant-4 \\\"May be improved\\\"\\n \\\"Campaign A\\\": [0.3, 0.6]\\n \\\"Campaign B\\\": [0.45, 0.23]\\n \\\"Campaign C\\\": [0.57, 0.69]\\n \\\"Campaign D\\\": [0.78, 0.34]\\n \\\"Campaign E\\\": [0.40, 0.34]\\n \\\"Campaign F\\\": [0.35, 0.78]\\n \\\"Our Target Product\\\": [0.5, 0.6]\",\n \"Requirement Analysis\": \"\",\n \"Requirement Pool\": [\n [\n \"P0\",\n \"The main code ...\"\n ],\n [\n \"P0\",\n \"The game algorithm ...\"\n ]\n ],\n \"UI Design draft\": \"Basic function description with a simple style and layout.\",\n \"Anything UNCLEAR\": \"\"\n}\n[/CONTENT]\n\n## nodes: \": # \"\n- Language: # Provide the language used in the project, typically matching the user's requirement language.\n- Programming Language: # Python/JavaScript or other mainstream programming language.\n- Original Requirements: # Place the original user's requirements here.\n- Product Goals: typing.List[str] # Provide up to three clear, orthogonal product goals.\n- User Stories: typing.List[str] # Provide up to 3 to 5 scenario-based user stories.\n- Competitive Analysis: typing.List[str] # Provide 5 to 7 competitive products.\n- Competitive Quadrant Chart: # Use mermaid quadrantChart syntax. Distribute scores evenly between 0 and 1\n- Requirement Analysis: # Provide a detailed analysis of the requirements.\n- Requirement Pool: typing.List[typing.List[str]] # List down the top-5 requirements with their priority (P0, P1, P2).\n- UI Design draft: # Provide a simple description of UI elements, functions, style, and layout.\n- Anything UNCLEAR: # Mention any aspects of the project that are unclear and try to clarify them.\n\n\n## constraint\nLanguage: Please use the same language as Human INPUT.\nFormat: output wrapped inside [CONTENT][/CONTENT] like format example, nothing else.\n\n## action\nFollow instructions of nodes, generate output and make sure it follows the format example.\n": "[CONTENT]\n{\n \"Language\": \"en_us\",\n \"Programming Language\": \"Python\",\n \"Original Requirements\": \"\",\n \"Product Goals\": [],\n \"User Stories\": [],\n \"Competitive Analysis\": [],\n \"Competitive Quadrant Chart\": \"quadrantChart\\n title \\\"Reach and engagement of campaigns\\\"\\n x-axis \\\"Low Reach\\\" --> \\\"High Reach\\\"\\n y-axis \\\"Low Engagement\\\" --> \\\"High Engagement\\\"\\n quadrant-1 \\\"We should expand\\\"\\n quadrant-2 \\\"Need to promote\\\"\\n quadrant-3 \\\"Re-evaluate\\\"\\n quadrant-4 \\\"May be improved\\\"\\n \\\"Campaign A\\\": [0.3, 0.6]\\n \\\"Campaign B\\\": [0.45, 0.23]\\n \\\"Campaign C\\\": [0.57, 0.69]\\n \\\"Campaign D\\\": [0.78, 0.34]\\n \\\"Campaign E\\\": [0.40, 0.34]\\n \\\"Campaign F\\\": [0.35, 0.78]\\n \\\"Our Target Product\\\": [0.5, 0.6]\",\n \"Requirement Analysis\": \"\",\n \"Requirement Pool\": [],\n \"UI Design draft\": \"Basic function description with a simple style and layout.\",\n \"Anything UNCLEAR\": \"\"\n}\n[/CONTENT]",
+ "\n## context\n\n### Project Name\n\n\n### Original Requirements\n需要一个基于LLM做总结的搜索引擎\n\n### Search Information\n-\n\n\n-----\n\n## format example\n[CONTENT]\n{\n \"Language\": \"en_us\",\n \"Programming Language\": \"Python\",\n \"Original Requirements\": \"Create a 2048 game\",\n \"Project Name\": \"game_2048\",\n \"Product Goals\": [\n \"Create an engaging user experience\",\n \"Improve accessibility, be responsive\",\n \"More beautiful UI\"\n ],\n \"User Stories\": [\n \"As a player, I want to be able to choose difficulty levels\",\n \"As a player, I want to see my score after each game\",\n \"As a player, I want to get restart button when I lose\",\n \"As a player, I want to see beautiful UI that make me feel good\",\n \"As a player, I want to play game via mobile phone\"\n ],\n \"Competitive Analysis\": [\n \"2048 Game A: Simple interface, lacks responsive features\",\n \"play2048.co: Beautiful and responsive UI with my best score shown\",\n \"2048game.com: Responsive UI with my best score shown, but many ads\"\n ],\n \"Competitive Quadrant Chart\": \"quadrantChart\\n title \\\"Reach and engagement of campaigns\\\"\\n x-axis \\\"Low Reach\\\" --> \\\"High Reach\\\"\\n y-axis \\\"Low Engagement\\\" --> \\\"High Engagement\\\"\\n quadrant-1 \\\"We should expand\\\"\\n quadrant-2 \\\"Need to promote\\\"\\n quadrant-3 \\\"Re-evaluate\\\"\\n quadrant-4 \\\"May be improved\\\"\\n \\\"Campaign A\\\": [0.3, 0.6]\\n \\\"Campaign B\\\": [0.45, 0.23]\\n \\\"Campaign C\\\": [0.57, 0.69]\\n \\\"Campaign D\\\": [0.78, 0.34]\\n \\\"Campaign E\\\": [0.40, 0.34]\\n \\\"Campaign F\\\": [0.35, 0.78]\\n \\\"Our Target Product\\\": [0.5, 0.6]\",\n \"Requirement Analysis\": \"\",\n \"Requirement Pool\": [\n [\n \"P0\",\n \"The main code ...\"\n ],\n [\n \"P0\",\n \"The game algorithm ...\"\n ]\n ],\n \"UI Design draft\": \"Basic function description with a simple style and layout.\",\n \"Anything UNCLEAR\": \"\"\n}\n[/CONTENT]\n\n## nodes: \": # \"\n- Language: # Provide the language used in the project, typically matching the user's requirement language.\n- Programming Language: # Python/JavaScript or other mainstream programming language.\n- Original Requirements: # Place the original user's requirements here.\n- Project Name: # According to the content of \"Original Requirements,\" name the project using snake case style , like 'game_2048' or 'simple_crm.\n- Product Goals: typing.List[str] # Provide up to three clear, orthogonal product goals.\n- User Stories: typing.List[str] # Provide up to 3 to 5 scenario-based user stories.\n- Competitive Analysis: typing.List[str] # Provide 5 to 7 competitive products.\n- Competitive Quadrant Chart: # Use mermaid quadrantChart syntax. Distribute scores evenly between 0 and 1\n- Requirement Analysis: # Provide a detailed analysis of the requirements.\n- Requirement Pool: typing.List[typing.List[str]] # List down the top-5 requirements with their priority (P0, P1, P2).\n- UI Design draft: # Provide a simple description of UI elements, functions, style, and layout.\n- Anything UNCLEAR: # Mention any aspects of the project that are unclear and try to clarify them.\n\n\n## constraint\nLanguage: Please use the same language as Human INPUT.\nFormat: output wrapped inside [CONTENT][/CONTENT] like format example, nothing else.\n\n## action\nFollow instructions of nodes, generate output and make sure it follows the format example.\n": "[CONTENT]\n{\n \"Language\": \"zh_cn\",\n \"Programming Language\": \"LLM\",\n \"Original Requirements\": \"需要一个基于LLM做总结的搜索引擎\",\n \"Project Name\": \"llm_summary_search_engine\",\n \"Product Goals\": [\n \"提供准确的搜索结果\",\n \"提高搜索引擎的效率\",\n \"优化用户体验\"\n ],\n \"User Stories\": [\n \"作为用户,我希望能够快速找到我需要的信息\",\n \"作为用户,我希望搜索结果准确无误\",\n \"作为用户,我希望搜索引擎能够智能推荐相关内容\"\n ],\n \"Competitive Analysis\": [\n \"搜索引擎A: 提供准确的搜索结果,但界面简陋\",\n \"搜索引擎B: 界面美观,但搜索结果不够准确\",\n \"搜索引擎C: 搜索结果准确,但速度较慢\"\n ],\n \"Competitive Quadrant Chart\": \"quadrantChart\\n title \\\"搜索引擎比较\\\"\\n x-axis \\\"低准确性\\\" --> \\\"高准确性\\\"\\n y-axis \\\"低速度\\\" --> \\\"高速度\\\"\\n quadrant-1 \\\"需要改进\\\"\\n quadrant-2 \\\"值得推广\\\"\\n quadrant-3 \\\"重新评估\\\"\\n quadrant-4 \\\"需要扩展\\\"\\n \\\"搜索引擎A\\\": [0.8, 0.3]\\n \\\"搜索引擎B\\\": [0.5, 0.7]\\n \\\"搜索引擎C\\\": [0.9, 0.2]\\n \\\"我们的搜索引擎\\\": [0.7, 0.6]\",\n \"Requirement Analysis\": \"\",\n \"Requirement Pool\": [\n [\n \"P0\",\n \"实现LLM技术进行文本摘要\"\n ],\n [\n \"P0\",\n \"构建搜索算法,提高搜索效率\"\n ],\n [\n \"P1\",\n \"设计智能推荐系统\"\n ]\n ],\n \"UI Design draft\": \"简洁的搜索框,清晰的搜索结果页面,智能推荐模块\",\n \"Anything UNCLEAR\": \"\"\n}\n[/CONTENT]",
+ "\n## context\n\n### Project Name\n\n\n### Original Requirements\nMake a cli snake game\n\n### Search Information\n-\n\n\n-----\n\n## format example\n[CONTENT]\n{\n \"Language\": \"en_us\",\n \"Programming Language\": \"Python\",\n \"Original Requirements\": \"Create a 2048 game\",\n \"Project Name\": \"game_2048\",\n \"Product Goals\": [\n \"Create an engaging user experience\",\n \"Improve accessibility, be responsive\",\n \"More beautiful UI\"\n ],\n \"User Stories\": [\n \"As a player, I want to be able to choose difficulty levels\",\n \"As a player, I want to see my score after each game\",\n \"As a player, I want to get restart button when I lose\",\n \"As a player, I want to see beautiful UI that make me feel good\",\n \"As a player, I want to play game via mobile phone\"\n ],\n \"Competitive Analysis\": [\n \"2048 Game A: Simple interface, lacks responsive features\",\n \"play2048.co: Beautiful and responsive UI with my best score shown\",\n \"2048game.com: Responsive UI with my best score shown, but many ads\"\n ],\n \"Competitive Quadrant Chart\": \"quadrantChart\\n title \\\"Reach and engagement of campaigns\\\"\\n x-axis \\\"Low Reach\\\" --> \\\"High Reach\\\"\\n y-axis \\\"Low Engagement\\\" --> \\\"High Engagement\\\"\\n quadrant-1 \\\"We should expand\\\"\\n quadrant-2 \\\"Need to promote\\\"\\n quadrant-3 \\\"Re-evaluate\\\"\\n quadrant-4 \\\"May be improved\\\"\\n \\\"Campaign A\\\": [0.3, 0.6]\\n \\\"Campaign B\\\": [0.45, 0.23]\\n \\\"Campaign C\\\": [0.57, 0.69]\\n \\\"Campaign D\\\": [0.78, 0.34]\\n \\\"Campaign E\\\": [0.40, 0.34]\\n \\\"Campaign F\\\": [0.35, 0.78]\\n \\\"Our Target Product\\\": [0.5, 0.6]\",\n \"Requirement Analysis\": \"\",\n \"Requirement Pool\": [\n [\n \"P0\",\n \"The main code ...\"\n ],\n [\n \"P0\",\n \"The game algorithm ...\"\n ]\n ],\n \"UI Design draft\": \"Basic function description with a simple style and layout.\",\n \"Anything UNCLEAR\": \"\"\n}\n[/CONTENT]\n\n## nodes: \": # \"\n- Language: # Provide the language used in the project, typically matching the user's requirement language.\n- Programming Language: # Python/JavaScript or other mainstream programming language.\n- Original Requirements: # Place the original user's requirements here.\n- Project Name: # According to the content of \"Original Requirements,\" name the project using snake case style , like 'game_2048' or 'simple_crm.\n- Product Goals: typing.List[str] # Provide up to three clear, orthogonal product goals.\n- User Stories: typing.List[str] # Provide up to 3 to 5 scenario-based user stories.\n- Competitive Analysis: typing.List[str] # Provide 5 to 7 competitive products.\n- Competitive Quadrant Chart: # Use mermaid quadrantChart syntax. Distribute scores evenly between 0 and 1\n- Requirement Analysis: # Provide a detailed analysis of the requirements.\n- Requirement Pool: typing.List[typing.List[str]] # List down the top-5 requirements with their priority (P0, P1, P2).\n- UI Design draft: # Provide a simple description of UI elements, functions, style, and layout.\n- Anything UNCLEAR: # Mention any aspects of the project that are unclear and try to clarify them.\n\n\n## constraint\nLanguage: Please use the same language as Human INPUT.\nFormat: output wrapped inside [CONTENT][/CONTENT] like format example, nothing else.\n\n## action\nFollow instructions of nodes, generate output and make sure it follows the format example.\n": "[CONTENT]\n{\n \"Language\": \"en_us\",\n \"Programming Language\": \"Python\",\n \"Original Requirements\": \"Make a cli snake game\",\n \"Project Name\": \"cli_snake_game\",\n \"Product Goals\": [\n \"Create an engaging user experience\",\n \"Improve accessibility and responsiveness\",\n \"Enhance the game with additional features\"\n ],\n \"User Stories\": [\n \"As a player, I want to control the snake using arrow keys\",\n \"As a player, I want to see my score during the game\",\n \"As a player, I want to have the option to restart the game\",\n \"As a player, I want to see a visually appealing UI\",\n \"As a player, I want to play the game on different platforms\"\n ],\n \"Competitive Analysis\": [\n \"Snake Game A: Simple interface, lacks responsive features\",\n \"SnakeGame.co: Beautiful and responsive UI with high scores displayed\",\n \"SnakeGame.com: Responsive UI with high scores shown, but many ads\"\n ],\n \"Competitive Quadrant Chart\": \"quadrantChart\\n title \\\"Reach and engagement of snake games\\\"\\n x-axis \\\"Low Reach\\\" --> \\\"High Reach\\\"\\n y-axis \\\"Low Engagement\\\" --> \\\"High Engagement\\\"\\n quadrant-1 \\\"We should expand\\\"\\n quadrant-2 \\\"Need to promote\\\"\\n quadrant-3 \\\"Re-evaluate\\\"\\n quadrant-4 \\\"May be improved\\\"\\n \\\"Snake Game A\\\": [0.3, 0.6]\\n \\\"SnakeGame.co\\\": [0.45, 0.23]\\n \\\"SnakeGame.com\\\": [0.57, 0.69]\\n \\\"Our Target Product\\\": [0.5, 0.6]\",\n \"Requirement Analysis\": \"\",\n \"Requirement Pool\": [\n [\n \"P0\",\n \"The main code for controlling the snake and game logic\"\n ],\n [\n \"P1\",\n \"Implementing the scoring system and UI\"\n ],\n [\n \"P2\",\n \"Adding platform compatibility and restart functionality\"\n ]\n ],\n \"UI Design draft\": \"The game will have a simple and intuitive UI with clear controls and a visually appealing design.\",\n \"Anything UNCLEAR\": \"\"\n}\n[/CONTENT]",
+ "\n## context\n{\"Language\":\"en_us\",\"Programming Language\":\"Python\",\"Original Requirements\":\"Make a cli snake game\",\"Project Name\":\"cli_snake_game\",\"Product Goals\":[\"Create an engaging user experience\",\"Improve accessibility and responsiveness\",\"Enhance the game with additional features\"],\"User Stories\":[\"As a player, I want to control the snake using arrow keys\",\"As a player, I want to see my score during the game\",\"As a player, I want to have the option to restart the game\",\"As a player, I want to see a visually appealing UI\",\"As a player, I want to play the game on different platforms\"],\"Competitive Analysis\":[\"Snake Game A: Simple interface, lacks responsive features\",\"SnakeGame.co: Beautiful and responsive UI with high scores displayed\",\"SnakeGame.com: Responsive UI with high scores shown, but many ads\"],\"Competitive Quadrant Chart\":\"quadrantChart\\n title \\\"Reach and engagement of snake games\\\"\\n x-axis \\\"Low Reach\\\" --> \\\"High Reach\\\"\\n y-axis \\\"Low Engagement\\\" --> \\\"High Engagement\\\"\\n quadrant-1 \\\"We should expand\\\"\\n quadrant-2 \\\"Need to promote\\\"\\n quadrant-3 \\\"Re-evaluate\\\"\\n quadrant-4 \\\"May be improved\\\"\\n \\\"Snake Game A\\\": [0.3, 0.6]\\n \\\"SnakeGame.co\\\": [0.45, 0.23]\\n \\\"SnakeGame.com\\\": [0.57, 0.69]\\n \\\"Our Target Product\\\": [0.5, 0.6]\",\"Requirement Analysis\":\"\",\"Requirement Pool\":[[\"P0\",\"The main code for controlling the snake and game logic\"],[\"P1\",\"Implementing the scoring system and UI\"],[\"P2\",\"Adding platform compatibility and restart functionality\"]],\"UI Design draft\":\"The game will have a simple and intuitive UI with clear controls and a visually appealing design.\",\"Anything UNCLEAR\":\"\"}\n\n-----\n\n## format example\n[CONTENT]\n{\n \"Implementation approach\": \"We will ...\",\n \"File list\": [\n \"main.py\",\n \"game.py\"\n ],\n \"Data structures and interfaces\": \"\\nclassDiagram\\n class Main {\\n -SearchEngine search_engine\\n +main() str\\n }\\n class SearchEngine {\\n -Index index\\n -Ranking ranking\\n -Summary summary\\n +search(query: str) str\\n }\\n class Index {\\n -KnowledgeBase knowledge_base\\n +create_index(data: dict)\\n +query_index(query: str) list\\n }\\n class Ranking {\\n +rank_results(results: list) list\\n }\\n class Summary {\\n +summarize_results(results: list) str\\n }\\n class KnowledgeBase {\\n +update(data: dict)\\n +fetch_data(query: str) dict\\n }\\n Main --> SearchEngine\\n SearchEngine --> Index\\n SearchEngine --> Ranking\\n SearchEngine --> Summary\\n Index --> KnowledgeBase\\n\",\n \"Program call flow\": \"\\nsequenceDiagram\\n participant M as Main\\n participant SE as SearchEngine\\n participant I as Index\\n participant R as Ranking\\n participant S as Summary\\n participant KB as KnowledgeBase\\n M->>SE: search(query)\\n SE->>I: query_index(query)\\n I->>KB: fetch_data(query)\\n KB-->>I: return data\\n I-->>SE: return results\\n SE->>R: rank_results(results)\\n R-->>SE: return ranked_results\\n SE->>S: summarize_results(ranked_results)\\n S-->>SE: return summary\\n SE-->>M: return summary\\n\",\n \"Anything UNCLEAR\": \"Clarification needed on third-party API integration, ...\"\n}\n[/CONTENT]\n\n## nodes: \": # \"\n- Implementation approach: