diff --git a/.coveragerc b/.coveragerc
new file mode 100644
index 000000000..905b611a6
--- /dev/null
+++ b/.coveragerc
@@ -0,0 +1,8 @@
+[run]
+source =
+ ./metagpt/
+omit =
+ */metagpt/ext/*
+ */metagpt/environment/android_env/*
+ */metagpt/environment/werewolf_env/*
+
\ No newline at end of file
diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
index a774d0ed1..01ab0342d 100644
--- a/.devcontainer/devcontainer.json
+++ b/.devcontainer/devcontainer.json
@@ -3,7 +3,7 @@
{
"name": "Python 3",
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
- "image": "mcr.microsoft.com/devcontainers/python:0-3.11",
+ "image": "metagpt/metagpt:latest",
// Features to add to the dev container. More info: https://containers.dev/features.
// "features": {},
@@ -18,7 +18,7 @@
]
}
},
-
+
// Use 'postCreateCommand' to run commands after the container is created.
"postCreateCommand": "./.devcontainer/postCreateCommand.sh"
diff --git a/.gitattributes b/.gitattributes
index 865da2ca2..e6436790e 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -14,6 +14,7 @@
*.ico binary
*.jpeg binary
*.mp3 binary
+*.mp4 binary
*.zip binary
*.bin binary
diff --git a/.github/workflows/fulltest.yaml b/.github/workflows/fulltest.yaml
index 70c800481..32eb3da00 100644
--- a/.github/workflows/fulltest.yaml
+++ b/.github/workflows/fulltest.yaml
@@ -30,7 +30,10 @@ jobs:
cache: 'pip'
- name: Install dependencies
run: |
- sh tests/scripts/run_install_deps.sh
+ python -m pip install --upgrade pip
+ pip install -e .[test]
+ npm install -g @mermaid-js/mermaid-cli
+ playwright install --with-deps
- name: Run reverse proxy script for ssh service
if: contains(github.ref, '-debugger')
continue-on-error: true
@@ -76,8 +79,8 @@ jobs:
./tests/data/rsp_cache_new.json
retention-days: 3
if: ${{ always() }}
- - name: Upload coverage reports to Codecov
- uses: codecov/codecov-action@v3
- env:
- CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
- if: ${{ always() }}
+ # - name: Upload coverage reports to Codecov
+ # uses: codecov/codecov-action@v3
+ # env:
+ # CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
+ # if: ${{ always() }}
diff --git a/.github/workflows/pre-commit.yaml b/.github/workflows/pre-commit.yaml
index ed4bbb144..d350a87f1 100644
--- a/.github/workflows/pre-commit.yaml
+++ b/.github/workflows/pre-commit.yaml
@@ -11,6 +11,7 @@ on:
jobs:
pre-commit-check:
runs-on: ubuntu-latest
+ environment: pre-commit
steps:
- name: Checkout Source Code
uses: actions/checkout@v2
diff --git a/.github/workflows/stale.yaml b/.github/workflows/stale.yaml
new file mode 100644
index 000000000..21627d5fb
--- /dev/null
+++ b/.github/workflows/stale.yaml
@@ -0,0 +1,22 @@
+name: Close inactive issues
+on:
+ schedule:
+ - cron: "5 0 * * *"
+
+jobs:
+ close-issues:
+ runs-on: ubuntu-latest
+ permissions:
+ issues: write
+ pull-requests: write
+ steps:
+ - uses: actions/stale@v5
+ with:
+ days-before-issue-stale: 30
+ days-before-issue-close: 14
+ stale-issue-label: "inactive"
+ stale-issue-message: "This issue has no activity in the past 30 days. Please comment on the issue if you have anything to add."
+ close-issue-message: "This issue was closed due to 45 days of inactivity. If you feel this issue is still relevant, please reopen the issue to continue the discussion."
+ days-before-pr-stale: -1
+ days-before-pr-close: -1
+ repo-token: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.github/workflows/unittest.yaml b/.github/workflows/unittest.yaml
index afa9faba7..15cb83df3 100644
--- a/.github/workflows/unittest.yaml
+++ b/.github/workflows/unittest.yaml
@@ -27,20 +27,23 @@ jobs:
cache: 'pip'
- name: Install dependencies
run: |
- sh tests/scripts/run_install_deps.sh
+ python -m pip install --upgrade pip
+ pip install -e .[test]
+ npm install -g @mermaid-js/mermaid-cli
+ playwright install --with-deps
- name: Test with pytest
run: |
export ALLOW_OPENAI_API_CALL=0
mkdir -p ~/.metagpt && cp tests/config2.yaml ~/.metagpt/config2.yaml
- pytest tests/ --doctest-modules --cov=./metagpt/ --cov-report=xml:cov.xml --cov-report=html:htmlcov --durations=20 | tee unittest.txt
+ pytest | tee unittest.txt
- name: Show coverage report
run: |
coverage report -m
- name: Show failed tests and overall summary
run: |
grep -E "FAILED tests|ERROR tests|[0-9]+ passed," unittest.txt
- failed_count=$(grep -E "FAILED|ERROR" unittest.txt | wc -l)
- if [[ "$failed_count" -gt 0 ]]; then
+ failed_count=$(grep -E "FAILED tests|ERROR tests" unittest.txt | wc -l | tr -d '[:space:]')
+ if [[ $failed_count -gt 0 ]]; then
echo "$failed_count failed lines found! Task failed."
exit 1
fi
@@ -54,8 +57,3 @@ jobs:
./tests/data/rsp_cache_new.json
retention-days: 3
if: ${{ always() }}
- - name: Upload coverage reports to Codecov
- uses: codecov/codecov-action@v3
- env:
- CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
- if: ${{ always() }}
diff --git a/.gitignore b/.gitignore
index 73f32f75d..7434aec84 100644
--- a/.gitignore
+++ b/.gitignore
@@ -178,6 +178,7 @@ tmp.png
.dependencies.json
tests/metagpt/utils/file_repo_git
tests/data/rsp_cache_new.json
+tests/data/serdeser_storage/
*.tmp
*.png
htmlcov
@@ -191,3 +192,9 @@ cov.xml
*.dot
.python-version
tests/data/requirements/*.jpg
+*.csv
+metagpt/ext/sela/results/*
+.chainlit/
+
+metagpt/ext/aflow/data
+metagpt/ext/aflow/scripts/optimized
diff --git a/README.md b/README.md
index 44fcfab18..e42b5b766 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@
# MetaGPT: The Multi-Agent Framework
-
+
@@ -12,6 +12,7 @@ # MetaGPT: The Multi-Agent Framework
+
@@ -22,22 +23,26 @@ # MetaGPT: The Multi-Agent Framework
-
+
## News
-🚀 Mar. 29, 2024: [v0.8.0](https://github.com/geekan/MetaGPT/releases/tag/v0.8.0) released. Now you can use Data Interpreter via pypi package import. Meanwhile, we integrated RAG module and supported multiple new LLMs.
+🚀 Feb. 19, 2025: Today we are officially launching our natural language programming product: MGX (MetaGPT X) - the world's first AI agent development team. [Offical website](https://mgx.dev/) [Twitter](https://x.com/MetaGPT_/status/1892199535130329356)
-🚀 Mar. 14, 2024: Our **Data Interpreter** paper is on [arxiv](https://arxiv.org/abs/2402.18679). Check the [example](https://docs.deepwisdom.ai/main/en/DataInterpreter/) and [code](https://github.com/geekan/MetaGPT/tree/main/examples/di)!
+🚀 Jan. 22, 2025: Our paper [AFlow: Automating Agentic Workflow Generation](https://openreview.net/forum?id=z5uVAKwmjf) accepted for **oral presentation (top 1.8%)** at ICLR 2025, **ranking #2** in the LLM-based Agent category.
+
+🚀 Oct. 29, 2024: We introduced three papers: [AFLOW](https://arxiv.org/abs/2410.10762), [FACT](https://arxiv.org/abs/2410.21012), and [SELA](https://arxiv.org/abs/2410.17238), check the [code](examples)!
+
+🚀 Mar. 29, 2024: [v0.8.0](https://github.com/geekan/MetaGPT/releases/tag/v0.8.0) released. Now you can use Data Interpreter ([arxiv](https://arxiv.org/abs/2402.18679), [example](https://docs.deepwisdom.ai/main/en/DataInterpreter/), [code](https://github.com/geekan/MetaGPT/tree/main/examples/di)) via pypi package import. Meanwhile, we integrated the RAG module and supported multiple new LLMs.
🚀 Feb. 08, 2024: [v0.7.0](https://github.com/geekan/MetaGPT/releases/tag/v0.7.0) released, supporting assigning different LLMs to different Roles. We also introduced [Data Interpreter](https://github.com/geekan/MetaGPT/blob/main/examples/di/README.md), a powerful agent capable of solving a wide range of real-world problems.
🚀 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.
+](https://openreview.net/forum?id=VtmBAGCN7o) 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.
+🚀 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).
@@ -61,7 +66,7 @@ ## Get Started
### Installation
-> Ensure that Python 3.9+ is installed on your system. You can check this by using: `python --version`.
+> Ensure that Python 3.9 or later, but less than 3.12, is installed on your system. You can check this by using: `python --version`.
> You can use conda like this: `conda create -n metagpt python=3.9 && conda activate metagpt`
```bash
@@ -85,8 +90,8 @@ # Check https://docs.deepwisdom.ai/main/en/guide/get_started/configuration.html
```yaml
llm:
- api_type: "openai" # or azure / ollama / open_llm etc. Check LLMType for more options
- model: "gpt-4-turbo-preview" # or gpt-3.5-turbo-1106 / gpt-4-1106-preview
+ api_type: "openai" # or azure / ollama / groq etc. Check LLMType for more options
+ model: "gpt-4-turbo" # or gpt-3.5-turbo
base_url: "https://api.openai.com/v1" # or forward url / other llm url
api_key: "YOUR_API_KEY"
```
@@ -107,7 +112,7 @@ ### Usage
print(repo) # it will print the repo structure with files
```
-You can also use its [Data Interpreter](https://github.com/geekan/MetaGPT/tree/main/examples/di)
+You can also use [Data Interpreter](https://github.com/geekan/MetaGPT/tree/main/examples/di) to write code:
```python
import asyncio
@@ -122,7 +127,7 @@ ### Usage
### QuickStart & Demo Video
-- Try it on [MetaGPT Huggingface Space](https://huggingface.co/spaces/deepwisdom/MetaGPT)
+- Try it on [MetaGPT Huggingface Space](https://huggingface.co/spaces/deepwisdom/MetaGPT-SoftwareCompany)
- [Matthew Berman: How To Install MetaGPT - Build A Startup With One Prompt!!](https://youtu.be/uT75J_KG_aY)
- [Official Demo Video](https://github.com/geekan/MetaGPT/assets/2707039/5e8c1062-8c35-440f-bb20-2b0320f8d27d)
@@ -142,7 +147,7 @@ ## Tutorial
- [Data Interpreter](https://docs.deepwisdom.ai/main/en/guide/use_cases/agent/interpreter/intro.html)
- [Debate](https://docs.deepwisdom.ai/main/en/guide/use_cases/multi_agent/debate.html)
- [Researcher](https://docs.deepwisdom.ai/main/en/guide/use_cases/agent/researcher.html)
- - [Recepit Assistant](https://docs.deepwisdom.ai/main/en/guide/use_cases/agent/receipt_assistant.html)
+ - [Receipt Assistant](https://docs.deepwisdom.ai/main/en/guide/use_cases/agent/receipt_assistant.html)
- ❓ [FAQs](https://docs.deepwisdom.ai/main/en/guide/faq.html)
## Support
@@ -168,16 +173,56 @@ ## Citation
To stay updated with the latest research and development, follow [@MetaGPT_](https://twitter.com/MetaGPT_) on Twitter.
-To cite [MetaGPT](https://arxiv.org/abs/2308.00352) or [Data Interpreter](https://arxiv.org/abs/2402.18679) in publications, please use the following BibTeX entries.
+To cite [MetaGPT](https://openreview.net/forum?id=VtmBAGCN7o) or [Data Interpreter](https://arxiv.org/abs/2402.18679) in publications, please use the following BibTeX entries.
```bibtex
-@misc{hong2023metagpt,
- title={MetaGPT: Meta Programming for A Multi-Agent Collaborative Framework},
- author={Sirui Hong and Mingchen Zhuge and Jonathan Chen and Xiawu Zheng and Yuheng Cheng and Ceyao Zhang and Jinlin Wang and Zili Wang and Steven Ka Shing Yau and Zijuan Lin and Liyang Zhou and Chenyu Ran and Lingfeng Xiao and Chenglin Wu and Jürgen Schmidhuber},
- year={2023},
- eprint={2308.00352},
+@inproceedings{hong2024metagpt,
+ title={Meta{GPT}: Meta Programming for A Multi-Agent Collaborative Framework},
+ author={Sirui Hong and Mingchen Zhuge and Jonathan Chen and Xiawu Zheng and Yuheng Cheng and Jinlin Wang and Ceyao Zhang and Zili Wang and Steven Ka Shing Yau and Zijuan Lin and Liyang Zhou and Chenyu Ran and Lingfeng Xiao and Chenglin Wu and J{\"u}rgen Schmidhuber},
+ booktitle={The Twelfth International Conference on Learning Representations},
+ year={2024},
+ url={https://openreview.net/forum?id=VtmBAGCN7o}
+}
+@misc{teng2025atom,
+ title={Atom of Thoughts for Markov LLM Test-Time Scaling},
+ author={Fengwei Teng and Zhaoyang Yu and Quan Shi and Jiayi Zhang and Chenglin Wu and Yuyu Luo},
+ year={2025},
+ eprint={2502.12018},
archivePrefix={arXiv},
- primaryClass={cs.AI}
+ primaryClass={cs.CL},
+ url={https://arxiv.org/abs/2502.12018},
+}
+@misc{xiang2025self,
+ title={Self-Supervised Prompt Optimization},
+ author={Jinyu Xiang and Jiayi Zhang and Zhaoyang Yu and Fengwei Teng and Jinhao Tu and Xinbing Liang and Sirui Hong and Chenglin Wu and Yuyu Luo},
+ year={2025},
+ eprint={2502.06855},
+ archivePrefix={arXiv},
+ primaryClass={cs.CL},
+ url={https://arxiv.org/abs/2502.06855},
+}
+@inproceedings{wang2025fact,
+ title={FACT: Examining the Effectiveness of Iterative Context Rewriting for Multi-fact Retrieval},
+ author={Jinlin Wang and Suyuchen Wang and Ziwen Xia and Sirui Hong and Yun Zhu and Bang Liu and Chenglin Wu},
+ booktitle={The 2025 Annual Conference of the Nations of the Americas Chapter of the ACL},
+ year={2025},
+ url={https://openreview.net/forum?id=VXOircx5h3}
+}
+@misc{chi2024sela,
+ title={SELA: Tree-Search Enhanced LLM Agents for Automated Machine Learning},
+ author={Yizhou Chi and Yizhang Lin and Sirui Hong and Duyi Pan and Yaying Fei and Guanghao Mei and Bangbang Liu and Tianqi Pang and Jacky Kwok and Ceyao Zhang and Bang Liu and Chenglin Wu},
+ year={2024},
+ eprint={2410.17238},
+ archivePrefix={arXiv},
+ primaryClass={cs.AI},
+ url={https://arxiv.org/abs/2410.17238},
+}
+@inproceedings{zhang2025aflow,
+ title={{AF}low: Automating Agentic Workflow Generation},
+ author={Jiayi Zhang and Jinyu Xiang and Zhaoyang Yu and Fengwei Teng and Xiong-Hui Chen and Jiaqi Chen and Mingchen Zhuge and Xin Cheng and Sirui Hong and Jinlin Wang and Bingnan Zheng and Bang Liu and Yuyu Luo and Chenglin Wu},
+ booktitle={The Thirteenth International Conference on Learning Representations},
+ year={2025},
+ url={https://openreview.net/forum?id=z5uVAKwmjf}
}
@misc{hong2024data,
title={Data Interpreter: An LLM Agent For Data Science},
@@ -185,8 +230,7 @@ ## Citation
year={2024},
eprint={2402.18679},
archivePrefix={arXiv},
- primaryClass={cs.AI}
+ primaryClass={cs.AI},
+ url={https://arxiv.org/abs/2402.18679},
}
-
```
-
diff --git a/config/config2.example.yaml b/config/config2.example.yaml
index f6970dabe..eb556fecf 100644
--- a/config/config2.example.yaml
+++ b/config/config2.example.yaml
@@ -1,17 +1,24 @@
llm:
- api_type: "openai" # or azure / ollama / open_llm etc. Check LLMType for more options
+ api_type: "openai" # or azure / ollama / groq etc.
base_url: "YOUR_BASE_URL"
api_key: "YOUR_API_KEY"
- model: "gpt-4-turbo-preview" # or gpt-3.5-turbo-1106 / gpt-4-1106-preview
+ model: "gpt-4-turbo" # or gpt-3.5-turbo
proxy: "YOUR_PROXY" # for LLM API requests
# timeout: 600 # Optional. If set to 0, default value is 300.
- pricing_plan: "" # Optional. If invalid, it will be automatically filled in with the value of the `model`.
- # Azure-exclusive pricing plan mappings:
- # - gpt-3.5-turbo 4k: "gpt-3.5-turbo-1106"
- # - gpt-4-turbo: "gpt-4-turbo-preview"
- # - gpt-4-turbo-vision: "gpt-4-vision-preview"
- # - gpt-4 8k: "gpt-4"
- # See for more: https://azure.microsoft.com/en-us/pricing/details/cognitive-services/openai-service/
+ # Details: https://azure.microsoft.com/en-us/pricing/details/cognitive-services/openai-service/
+ pricing_plan: "" # Optional. Use for Azure LLM when its model name is not the same as OpenAI's
+
+
+# RAG Embedding.
+# For backward compatibility, if the embedding is not set and the llm's api_type is either openai or azure, the llm's config will be used.
+embedding:
+ api_type: "" # openai / azure / gemini / ollama etc. Check EmbeddingType for more options.
+ base_url: ""
+ api_key: ""
+ model: ""
+ api_version: ""
+ embed_batch_size: 100
+ dimensions: # output dimension of embedding model
# Role's custom configuration
roles:
@@ -59,7 +66,7 @@ browser:
mermaid:
engine: "pyppeteer"
- path: "/Applications/Google Chrome.app"
+ pyppeteer_path: "/Applications/Google Chrome.app"
redis:
host: "YOUR_HOST"
@@ -98,3 +105,25 @@ iflytek_api_key: "YOUR_API_KEY"
iflytek_api_secret: "YOUR_API_SECRET"
metagpt_tti_url: "YOUR_MODEL_URL"
+
+omniparse:
+ api_key: "YOUR_API_KEY"
+ base_url: "YOUR_BASE_URL"
+
+models:
+# "YOUR_MODEL_NAME_1 or YOUR_API_TYPE_1": # model: "gpt-4-turbo" # or gpt-3.5-turbo
+# api_type: "openai" # or azure / ollama / groq etc.
+# base_url: "YOUR_BASE_URL"
+# api_key: "YOUR_API_KEY"
+# proxy: "YOUR_PROXY" # for LLM API requests
+# # timeout: 600 # Optional. If set to 0, default value is 300.
+# # Details: https://azure.microsoft.com/en-us/pricing/details/cognitive-services/openai-service/
+# pricing_plan: "" # Optional. Use for Azure LLM when its model name is not the same as OpenAI's
+# "YOUR_MODEL_NAME_2 or YOUR_API_TYPE_2": # api_type: "openai" # or azure / ollama / groq etc.
+# api_type: "openai" # or azure / ollama / groq etc.
+# base_url: "YOUR_BASE_URL"
+# api_key: "YOUR_API_KEY"
+# proxy: "YOUR_PROXY" # for LLM API requests
+# # timeout: 600 # Optional. If set to 0, default value is 300.
+# # Details: https://azure.microsoft.com/en-us/pricing/details/cognitive-services/openai-service/
+# pricing_plan: "" # Optional. Use for Azure LLM when its model name is not the same as OpenAI's
diff --git a/config/config2.yaml b/config/config2.yaml
index 8e5825b57..b3f24539c 100644
--- a/config/config2.yaml
+++ b/config/config2.yaml
@@ -1,7 +1,8 @@
# Full Example: https://github.com/geekan/MetaGPT/blob/main/config/config2.example.yaml
# Reflected Code: https://github.com/geekan/MetaGPT/blob/main/metagpt/config2.py
+# Config Docs: https://docs.deepwisdom.ai/main/en/guide/get_started/configuration.html
llm:
- api_type: "openai" # or azure / ollama / open_llm etc. Check LLMType for more options
- model: "gpt-4-turbo-preview" # or gpt-3.5-turbo-1106 / gpt-4-1106-preview
+ api_type: "openai" # or azure / ollama / groq etc.
+ model: "gpt-4-turbo" # or gpt-3.5-turbo
base_url: "https://api.openai.com/v1" # or forward url / other llm url
api_key: "YOUR_API_KEY"
\ No newline at end of file
diff --git a/config/examples/anthropic-claude-3-5-sonnet.yaml b/config/examples/anthropic-claude-3-5-sonnet.yaml
new file mode 100644
index 000000000..7c4df6064
--- /dev/null
+++ b/config/examples/anthropic-claude-3-5-sonnet.yaml
@@ -0,0 +1,5 @@
+llm:
+ api_type: 'claude' # or anthropic
+ base_url: 'https://api.anthropic.com'
+ api_key: 'YOUR_API_KEY'
+ model: 'claude-3-5-sonnet-20240620' # or 'claude-3-opus-20240229'
\ No newline at end of file
diff --git a/config/examples/aws-bedrock.yaml b/config/examples/aws-bedrock.yaml
new file mode 100644
index 000000000..d44fe8386
--- /dev/null
+++ b/config/examples/aws-bedrock.yaml
@@ -0,0 +1,10 @@
+llm:
+ api_type: 'bedrock'
+ access_key: 'YOUR_API_KEY'
+ secret_key: 'YOUR_API_SECRET'
+
+ region_name: "us-east-1"
+ model: "meta.llama2-70b-chat-v1"
+ # model: "anthropic.claude-3-sonnet-20240229-v1:0"
+ # model: "mistral.mixtral-8x7b-instruct-v0:1"
+ # model: "meta.llama2-13b-chat-v1"
\ No newline at end of file
diff --git a/config/examples/google-gemini.yaml b/config/examples/google-gemini.yaml
new file mode 100644
index 000000000..82a22bdf5
--- /dev/null
+++ b/config/examples/google-gemini.yaml
@@ -0,0 +1,4 @@
+llm:
+ api_type: 'gemini'
+ api_key: 'YOUR_API_KEY'
+ model: 'gemini-pro'
\ No newline at end of file
diff --git a/config/examples/groq-llama3-70b.yaml b/config/examples/groq-llama3-70b.yaml
new file mode 100644
index 000000000..93ff24b3d
--- /dev/null
+++ b/config/examples/groq-llama3-70b.yaml
@@ -0,0 +1,5 @@
+llm:
+ # Visit https://console.groq.com/keys to create api key
+ base_url: "https://api.groq.com/openai/v1"
+ api_key: "YOUR_API_KEY"
+ model: "llama3-70b-8192" # llama3-8b-8192,llama3-70b-8192,llama2-70b-4096 ,mixtral-8x7b-32768,gemma-7b-it
diff --git a/config/examples/huoshan_ark.yaml b/config/examples/huoshan_ark.yaml
new file mode 100644
index 000000000..b0516359b
--- /dev/null
+++ b/config/examples/huoshan_ark.yaml
@@ -0,0 +1,5 @@
+llm:
+ api_type: "ark"
+ model: "" # your model endpoint like ep-xxx
+ base_url: "https://ark.cn-beijing.volces.com/api/v3"
+ api_key: "" # your api-key like ey……
\ No newline at end of file
diff --git a/config/examples/openai-gpt-3.5-turbo.yaml b/config/examples/openai-gpt-3.5-turbo.yaml
new file mode 100644
index 000000000..41364842a
--- /dev/null
+++ b/config/examples/openai-gpt-3.5-turbo.yaml
@@ -0,0 +1,5 @@
+llm:
+ api_key: "YOUR_API_KEY"
+ model: "gpt-3.5-turbo"
+ #proxy: "http://:"
+ #base_url: "https:///v1"
diff --git a/config/examples/openai-gpt-4-turbo.yaml b/config/examples/openai-gpt-4-turbo.yaml
new file mode 100644
index 000000000..5765f460e
--- /dev/null
+++ b/config/examples/openai-gpt-4-turbo.yaml
@@ -0,0 +1,6 @@
+llm:
+ api_key: "YOUR_API_KEY"
+ model: "gpt-4-turbo"
+ #proxy: "http://:"
+ #base_url: "https:///v1"
+
diff --git a/config/examples/openrouter-llama3-70b-instruct.yaml b/config/examples/openrouter-llama3-70b-instruct.yaml
new file mode 100644
index 000000000..1850d7f5c
--- /dev/null
+++ b/config/examples/openrouter-llama3-70b-instruct.yaml
@@ -0,0 +1,5 @@
+llm:
+ api_type: openrouter
+ base_url: "https://openrouter.ai/api/v1"
+ api_key: "YOUR_API_KEY"
+ model: meta-llama/llama-3-70b-instruct
\ No newline at end of file
diff --git a/config/examples/spark_lite.yaml b/config/examples/spark_lite.yaml
new file mode 100644
index 000000000..15898e019
--- /dev/null
+++ b/config/examples/spark_lite.yaml
@@ -0,0 +1,10 @@
+# 适用于讯飞星火的spark-lite 参考 https://www.xfyun.cn/doc/spark/Web.html#_2-function-call%E8%AF%B4%E6%98%8E
+
+llm:
+ api_type: "spark"
+ # 对应模型的url 参考 https://www.xfyun.cn/doc/spark/Web.html#_1-%E6%8E%A5%E5%8F%A3%E8%AF%B4%E6%98%8E
+ base_url: "ws(s)://spark-api.xf-yun.com/v1.1/chat"
+ app_id: ""
+ api_key: ""
+ api_secret: ""
+ domain: "general" # 取值为 [general,generalv2,generalv3,generalv3.5] 和url一一对应
diff --git a/config/puppeteer-config.json b/config/puppeteer-config.json
index 7b2851c29..b74a514e7 100644
--- a/config/puppeteer-config.json
+++ b/config/puppeteer-config.json
@@ -1,6 +1,4 @@
{
- "executablePath": "/usr/bin/chromium",
- "args": [
- "--no-sandbox"
- ]
-}
\ No newline at end of file
+ "executablePath": "/usr/bin/chromium",
+ "args": ["--no-sandbox"]
+}
diff --git a/docs/FAQ-EN.md b/docs/FAQ-EN.md
index d3caa244e..f4c5fff15 100644
--- a/docs/FAQ-EN.md
+++ b/docs/FAQ-EN.md
@@ -38,9 +38,9 @@ ### Chief Evangelist (Monthly Rotation)
### FAQ
1. Code truncation/ Parsing failure:
- 1. Check if it's due to exceeding length. Consider using the gpt-4-turbo-preview or other long token versions.
+ 1. Check if it's due to exceeding length. Consider using the gpt-4-turbo or other long token versions.
2. Success rate:
- 1. There hasn't been a quantitative analysis yet, but the success rate of code generated by gpt-4-turbo-preview is significantly higher than that of gpt-3.5-turbo.
+ 1. There hasn't been a quantitative analysis yet, but the success rate of code generated by gpt-4-turbo is significantly higher than that of gpt-3.5-turbo.
3. Support for incremental, differential updates (if you wish to continue a half-done task):
1. There is now an experimental version. Specify `--inc --project-path ""` or `--inc --project-name ""` on the command line and enter the corresponding requirements to try it.
4. Can existing code be loaded?
diff --git a/docs/README_CN.md b/docs/README_CN.md
index 8aea5e4cb..88583cf24 100644
--- a/docs/README_CN.md
+++ b/docs/README_CN.md
@@ -9,19 +9,20 @@ # MetaGPT: 多智能体框架
-
-
-
+
+
+
+
-
+
-
+
1. MetaGPT输入**一句话的老板需求**,输出**用户故事 / 竞品分析 / 需求 / 数据结构 / APIs / 文件等**
@@ -76,7 +77,7 @@ # 步骤2: 使用容器运行metagpt演示
详细的安装请参考 [docker_install](https://docs.deepwisdom.ai/main/zh/guide/get_started/installation.html#%E4%BD%BF%E7%94%A8docker%E5%AE%89%E8%A3%85)
### 快速开始的演示视频
-- 在 [MetaGPT Huggingface Space](https://huggingface.co/spaces/deepwisdom/MetaGPT) 上进行体验
+- 在 [MetaGPT Huggingface Space](https://huggingface.co/spaces/deepwisdom/MetaGPT-SoftwareCompany) 上进行体验
- [Matthew Berman: How To Install MetaGPT - Build A Startup With One Prompt!!](https://youtu.be/uT75J_KG_aY)
- [官方演示视频](https://github.com/geekan/MetaGPT/assets/2707039/5e8c1062-8c35-440f-bb20-2b0320f8d27d)
@@ -119,13 +120,12 @@ ## 引用
如果您在研究论文中使用 MetaGPT 或 Data Interpreter,请引用我们的工作:
```bibtex
-@misc{hong2023metagpt,
- title={MetaGPT: Meta Programming for Multi-Agent Collaborative Framework},
- author={Sirui Hong and Xiawu Zheng and Jonathan Chen and Yuheng Cheng and Jinlin Wang and Ceyao Zhang and Zili Wang and Steven Ka Shing Yau and Zijuan Lin and Liyang Zhou and Chenyu Ran and Lingfeng Xiao and Chenglin Wu},
- year={2023},
- eprint={2308.00352},
- archivePrefix={arXiv},
- primaryClass={cs.AI}
+@inproceedings{hong2024metagpt,
+ title={Meta{GPT}: Meta Programming for A Multi-Agent Collaborative Framework},
+ author={Sirui Hong and Mingchen Zhuge and Jonathan Chen and Xiawu Zheng and Yuheng Cheng and Jinlin Wang and Ceyao Zhang and Zili Wang and Steven Ka Shing Yau and Zijuan Lin and Liyang Zhou and Chenyu Ran and Lingfeng Xiao and Chenglin Wu and J{\"u}rgen Schmidhuber},
+ booktitle={The Twelfth International Conference on Learning Representations},
+ year={2024},
+ url={https://openreview.net/forum?id=VtmBAGCN7o}
}
@misc{hong2024data,
title={Data Interpreter: An LLM Agent For Data Science},
diff --git a/docs/README_FR.md b/docs/README_FR.md
new file mode 100644
index 000000000..4bb02e0d4
--- /dev/null
+++ b/docs/README_FR.md
@@ -0,0 +1,194 @@
+
+# MetaGPT: Architecture Multi-Agent
+
+
+
+
+
+
+Assigner différents rôles aux GPTs pour former une entité collaborative capable de gérer des tâches complexes.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+## Nouveautés
+🚀 29 mars 2024: La version [v0.8.0](https://github.com/geekan/MetaGPT/releases/tag/v0.8.0) a été publiée. Vous pouvez désormais utiliser le Data Interpreter ([arxiv](https://arxiv.org/abs/2402.18679), [example](https://docs.deepwisdom.ai/main/en/DataInterpreter/), [code](https://github.com/geekan/MetaGPT/tree/main/examples/di)) via l'importation du package PyPI. De plus, le module RAG (Génération Augmentée par Récupération) a été intégré, et plusieurs nouveaux modèles de LLMs sont désormais pris en charge.
+
+🚀 28 février 2024: La version [v0.7.0](https://github.com/geekan/MetaGPT/releases/tag/v0.7.0) a été publiée, permettant l'attribution de différents modèles de langage (LLMs) à différents Rôles. Nous avons également introduit le [Data Interpreter](https://github.com/geekan/MetaGPT/blob/main/examples/di/README.md), , un agent puissant capable de résoudre une grande variété de problèmes du monde réel.
+
+🚀 16 janvier 2024: Notre article intitulé [MetaGPT: Meta Programming for A Multi-Agent Collaborative Framework
+](https://openreview.net/forum?id=VtmBAGCN7o) a été accepté pour une **présentation orale (top 1,2%)** à la conférence ICLR 2024, se **classant n°1** dans la catégorie des agents basés sur les modèles de langage (LLM).
+
+🚀 3 janvier 2024 : La version [v0.6.0](https://github.com/geekan/MetaGPT/releases/tag/v0.6.0) a été publiée avec de nouvelles fonctionnalités, notamment la sérialisation, la mise à niveau du package OpenAI et la prise en charge de plusieurs modèles de langage (LLM). Un [exemple minimal pour le débat](https://github.com/geekan/MetaGPT/blob/main/examples/debate_simple.py) a également été ajouté pour illustrer ces capacités.
+
+🚀 15 décembre 2023 : La version [v0.5.0](https://github.com/geekan/MetaGPT/releases/tag/v0.5.0) a été publiée, introduisant des fonctionnalités expérimentales telles que le développement incrémental, la prise en charge du multilingue, et la compatibilité avec plusieurs langages de programmation, etc..
+
+
+🔥 8 novembre 2023 : MetaGPT a été sélectionné parmi les [Open100: Top 100 des réalisations open source](https://www.benchcouncil.org/evaluation/opencs/annual.html), une reconnaissance qui met en avant les meilleures innovations et contributions dans le domaine des projets open source.
+
+🔥 1er septembre 2023 : MetaGPT a dominé le classement **GitHub Trending Monthly** pour la **17ème fois** en août 2023, consolidant ainsi sa position en tant que projet open source de premier plan.
+
+🌟 30 juin 2023 : MetaGPT est désormais open source, permettant à la communauté de contribuer et d'enrichir le projet.
+
+🌟 24 avril 2023 : La première ligne de code de MetaGPT a été engagée, marquant le début de ce projet innovant.
+
+
+### Système multi-agents dans une entreprise de logiciels
+
+1. **Exigence unique** : MetaGPT prend en entrée une **exigence formulée en une ligne** et produit des résultats variés, tels que des **user stories, des analyses concurrentielles, des exigences, des structures de données, des API, des documents, etc.**.
+
+2. **Structure interne** : MetaGPT intègre divers rôles présents dans une entreprise de logiciels, notamment **des chefs de produits, des architectes, des chefs de projet et des ingénieurs**. Ce système propose un processus complet de **développement logiciel**, soutenu par des **procédures opérationnelles standardisées (SOP) soigneusement orchestrées**.
+
+ 1. La philosophie centrale du système est exprimée par l'énoncé : `Code = SOP(Équipe)`. Cela signifie que les SOP sont concrétisées et appliquées à des équipes composées de modèles de langage (LLMs), permettant ainsi une meilleure gestion et un meilleur déroulement des projets.
+
+
+
+
+
Schéma multi-agent d'une entreprise de logiciels (Mise en œuvre progressive)
+
+
+## Commençons !
+
+### Installation
+
+> Assurez-vous que Python 3.9 ou supérieur, mais inférieur à 3.12, est installé sur votre système. Vous pouvez le vérifier en utilisant : `python --version`.
+> Vous pouvez utiliser conda comme suit : `conda create -n metagpt python=3.9 && conda activate metagpt`
+
+```bash
+pip install --upgrade metagpt
+# or `pip install --upgrade git+https://github.com/geekan/MetaGPT.git`
+# or `git clone https://github.com/geekan/MetaGPT && cd MetaGPT && pip install --upgrade -e .`
+```
+
+Pour des conseils d'installation détaillés, veuillez vous référer à [cli_install](https://docs.deepwisdom.ai/main/en/guide/get_started/installation.html#install-stable-version)
+ ou [docker_install](https://docs.deepwisdom.ai/main/en/guide/get_started/installation.html#install-with-docker)
+
+### Configuration
+
+Vous pouvez initialiser la configuration de MetaGPT en lançant la commande suivante, ou en créant manuellement le fichier `~/.metagpt/config2.yaml` :
+```bash
+# Visitez https://docs.deepwisdom.ai/main/en/guide/get_started/configuration.html pour plus de détails
+metagpt --init-config # il créera ~/.metagpt/config2.yaml, il suffit de le modifier selon vos besoins
+```
+
+Vous pouvez configurer `~/.metagpt/config2.yaml` selon l'[exemple](https://github.com/geekan/MetaGPT/blob/main/config/config2.example.yaml) et le [doc](https://docs.deepwisdom.ai/main/en/guide/get_started/configuration.html) :
+
+```yaml
+llm:
+ api_type: "openai" # ou azure / ollama / groq etc. Consultez LLMType pour plus d'options
+ model: "gpt-4-turbo" # ou gpt-3.5-turbo
+ base_url: "https://api.openai.com/v1" # ou URL de transfert / URL d'autre LLM.
+ api_key: "VOTRE_CLE_API"
+```
+
+### Utilisation
+
+Après l'installation, vous pouvez utiliser MetaGPT en CLI
+
+```bash
+metagpt "Create a 2048 game" # ceci créera un repo dans ./workspace
+```
+
+ou l'utiliser comme bibliothèque
+
+```python
+from metagpt.software_company import generate_repo, ProjectRepo
+repo: ProjectRepo = generate_repo("Create a 2048 game") # ou ProjectRepo("")
+print(repo) # il affichera la structure du repo avec les fichiers
+```
+
+Vous pouvez aussi utiliser [Data Interpreter](https://github.com/geekan/MetaGPT/tree/main/examples/di) pour écrire du code:
+
+```python
+import asyncio
+from metagpt.roles.di.data_interpreter import DataInterpreter
+
+async def main():
+ di = DataInterpreter()
+ await di.run("Exécuter une analyse de données sur le jeu de données sklearn Iris et y inclure un graphique")
+
+asyncio.run(main()) # ou attendre main() dans une configuration de notebook jupyter
+```
+
+
+### Vidéo de démonstration et de démarrage rapide (en Anglais) :
+- Essayez-le sur [MetaGPT Huggingface Space](https://huggingface.co/spaces/deepwisdom/MetaGPT)
+- [Matthew Berman : Comment installer MetaGPT - Construire une startup avec une seule invite](https://youtu.be/uT75J_KG_aY)
+- [Vidéo de démonstration officielle](https://github.com/geekan/MetaGPT/assets/2707039/5e8c1062-8c35-440f-bb20-2b0320f8d27d)
+
+https://github.com/geekan/MetaGPT/assets/34952977/34345016-5d13-489d-b9f9-b82ace413419
+
+## Tutoriel (en Anglais)
+
+- 🗒 [Document en ligne](https://docs.deepwisdom.ai/main/en/)
+- 💻 [Utilisation](https://docs.deepwisdom.ai/main/en/guide/get_started/quickstart.html)
+- 🔎 [Que peut faire MetaGPT](https://docs.deepwisdom.ai/main/en/guide/get_started/introduction.html)
+- 🛠 Comment créer ses propres agents ?
+ - [MetaGPT Guide d'utilisation et de développement | Agent 101](https://docs.deepwisdom.ai/main/en/guide/tutorials/agent_101.html)
+ - [MetaGPT Guide d'utilisation et de développement | MultiAgent 101](https://docs.deepwisdom.ai/main/en/guide/tutorials/multi_agent_101.html)
+- 🧑💻 Contribution
+ - [Élaborer une feuille de route](docs/ROADMAP.md)
+- 🔖 Cas d'usage
+ - [Interprète des données](https://docs.deepwisdom.ai/main/en/guide/use_cases/agent/interpreter/intro.html)
+ - [Débat](https://docs.deepwisdom.ai/main/en/guide/use_cases/multi_agent/debate.html)
+ - [Chercheur](https://docs.deepwisdom.ai/main/en/guide/use_cases/agent/researcher.html)
+ - [Assistant(e) de réception](https://docs.deepwisdom.ai/main/en/guide/use_cases/agent/receipt_assistant.html)
+- ❓ [FAQs](https://docs.deepwisdom.ai/main/en/guide/faq.html)
+
+## Support
+
+### Rejoignez-nous sur Discord
+
+📢 Rejoignez-nous sur [Discord Channel](https://discord.gg/ZRHeExS6xv)! Au plaisir de vous y voir ! 🎉
+
+### Formulaire de contribution
+
+📝 [Remplissez le formulaire](https://airtable.com/appInfdG0eJ9J4NNL/pagK3Fh1sGclBvVkV/form) pour devenir contributeur. Nous nous réjouissons de votre participation !
+
+### Information de contact
+
+Si vous avez des questions ou des commentaires sur ce projet, n'hésitez pas à nous contacter. Nous apprécions grandement vos suggestions !
+
+- **Email:** alexanderwu@deepwisdom.ai
+- **GitHub Issues:** Pour des questions plus techniques, vous pouvez également créer un nouveau problème dans notre [dépôt Github](https://github.com/geekan/metagpt/issues).
+
+Nous répondrons à toutes les questions dans un délai de 2 à 3 jours ouvrables.
+
+## Citation
+
+Pour rester informé des dernières recherches et développements, suivez [@MetaGPT_] (https://twitter.com/MetaGPT_) sur Twitter.
+
+Pour citer [MetaGPT](https://openreview.net/forum?id=VtmBAGCN7o) ou [Data Interpreter](https://arxiv.org/abs/2402.18679) dans des publications, veuillez utiliser les entrées BibTeX suivantes.
+
+```bibtex
+@inproceedings{hong2024metagpt,
+ title={Meta{GPT}: Meta Programming for A Multi-Agent Collaborative Framework},
+ author={Sirui Hong and Mingchen Zhuge and Jonathan Chen and Xiawu Zheng and Yuheng Cheng and Jinlin Wang and Ceyao Zhang and Zili Wang and Steven Ka Shing Yau and Zijuan Lin and Liyang Zhou and Chenyu Ran and Lingfeng Xiao and Chenglin Wu and J{\"u}rgen Schmidhuber},
+ booktitle={The Twelfth International Conference on Learning Representations},
+ year={2024},
+ url={https://openreview.net/forum?id=VtmBAGCN7o}
+}
+@misc{hong2024data,
+ title={Data Interpreter: An LLM Agent For Data Science},
+ author={Sirui Hong and Yizhang Lin and Bang Liu and Bangbang Liu and Binhao Wu and Danyang Li and Jiaqi Chen and Jiayi Zhang and Jinlin Wang and Li Zhang and Lingyao Zhang and Min Yang and Mingchen Zhuge and Taicheng Guo and Tuo Zhou and Wei Tao and Wenyi Wang and Xiangru Tang and Xiangtao Lu and Xiawu Zheng and Xinbing Liang and Yaying Fei and Yuheng Cheng and Zongze Xu and Chenglin Wu},
+ year={2024},
+ eprint={2402.18679},
+ archivePrefix={arXiv},
+ primaryClass={cs.AI}
+}
+```
diff --git a/docs/README_JA.md b/docs/README_JA.md
index 91155532b..fd96602b5 100644
--- a/docs/README_JA.md
+++ b/docs/README_JA.md
@@ -9,9 +9,10 @@ # MetaGPT: マルチエージェントフレームワーク
1. MetaGPT は、**1 行の要件** を入力とし、**ユーザーストーリー / 競合分析 / 要件 / データ構造 / API / 文書など** を出力します。
@@ -291,20 +292,19 @@ ## クイックスタート
- [MetaGPT クイックスタート](https://deepwisdom.feishu.cn/wiki/CyY9wdJc4iNqArku3Lncl4v8n2b)
Hugging Face Space で試す
-- https://huggingface.co/spaces/deepwisdom/MetaGPT
+- https://huggingface.co/spaces/deepwisdom/MetaGPT-SoftwareCompany
## 引用
研究論文でMetaGPTやData Interpreterを使用する場合は、以下のように当社の作業を引用してください:
```bibtex
-@misc{hong2023metagpt,
- title={MetaGPT: Meta Programming for A Multi-Agent Collaborative Framework},
- author={Sirui Hong and Mingchen Zhuge and Jonathan Chen and Xiawu Zheng and Yuheng Cheng and Ceyao Zhang and Jinlin Wang and Zili Wang and Steven Ka Shing Yau and Zijuan Lin and Liyang Zhou and Chenyu Ran and Lingfeng Xiao and Chenglin Wu and Jürgen Schmidhuber},
- year={2023},
- eprint={2308.00352},
- archivePrefix={arXiv},
- primaryClass={cs.AI}
+@inproceedings{hong2024metagpt,
+ title={Meta{GPT}: Meta Programming for A Multi-Agent Collaborative Framework},
+ author={Sirui Hong and Mingchen Zhuge and Jonathan Chen and Xiawu Zheng and Yuheng Cheng and Jinlin Wang and Ceyao Zhang and Zili Wang and Steven Ka Shing Yau and Zijuan Lin and Liyang Zhou and Chenyu Ran and Lingfeng Xiao and Chenglin Wu and J{\"u}rgen Schmidhuber},
+ booktitle={The Twelfth International Conference on Learning Representations},
+ year={2024},
+ url={https://openreview.net/forum?id=VtmBAGCN7o}
}
@misc{hong2024data,
title={Data Interpreter: An LLM Agent For Data Science},
diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md
index ec17cc0ce..452171971 100644
--- a/docs/ROADMAP.md
+++ b/docs/ROADMAP.md
@@ -55,29 +55,30 @@ ### Tasks
14. Implementation: AI training
15. ~~Implementation: Run code~~ (v0.2.1)
16. ~~Implementation: Web access~~ (v0.2.1)
-5. Plugins: Compatibility with plugin system
-6. Tools
+5. Tools
1. ~~Support SERPER api~~
2. ~~Support Selenium apis~~
3. ~~Support Playwright apis~~
-7. Roles
+ 4. Plugins: Compatibility with plugin system
+6. Roles
1. Perfect the action pool/skill pool for each role
2. E-commerce seller
3. ~~Data analyst~~ (v0.7.0)
4. News observer
5. ~~Institutional researcher~~ (v0.2.1)
-8. Evaluation
+ 6. User
+7. 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.8.0)
4. Reproduce papers, achieving SOTA results for current mathematical problem solving process (expected v0.8.0)
-9. LLM
- 1. Support Claude underlying API
+8. LLM
+ 1. ~~Support Claude underlying API~~
2. ~~Support Azure asynchronous API~~
- 3. Support streaming version of all APIs
+ 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~~
+9. 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~~
\ No newline at end of file
diff --git a/docs/resources/aflow/AFLOW-experiment.jpg b/docs/resources/aflow/AFLOW-experiment.jpg
new file mode 100644
index 000000000..dc7266c1e
Binary files /dev/null and b/docs/resources/aflow/AFLOW-experiment.jpg differ
diff --git a/docs/resources/aflow/AFLOW-method.jpg b/docs/resources/aflow/AFLOW-method.jpg
new file mode 100644
index 000000000..14ae60f49
Binary files /dev/null and b/docs/resources/aflow/AFLOW-method.jpg differ
diff --git a/docs/resources/aflow/AFLOW-performance.jpg b/docs/resources/aflow/AFLOW-performance.jpg
new file mode 100644
index 000000000..3866c40b9
Binary files /dev/null and b/docs/resources/aflow/AFLOW-performance.jpg differ
diff --git a/docs/resources/spo/SPO-closed_task_figure.png b/docs/resources/spo/SPO-closed_task_figure.png
new file mode 100644
index 000000000..329ff5461
Binary files /dev/null and b/docs/resources/spo/SPO-closed_task_figure.png differ
diff --git a/docs/resources/spo/SPO-closed_task_table.png b/docs/resources/spo/SPO-closed_task_table.png
new file mode 100644
index 000000000..f5bc16523
Binary files /dev/null and b/docs/resources/spo/SPO-closed_task_table.png differ
diff --git a/docs/resources/spo/SPO-logo.png b/docs/resources/spo/SPO-logo.png
new file mode 100644
index 000000000..717bdcb1b
Binary files /dev/null and b/docs/resources/spo/SPO-logo.png differ
diff --git a/docs/resources/spo/SPO-method.png b/docs/resources/spo/SPO-method.png
new file mode 100644
index 000000000..7d73a63c9
Binary files /dev/null and b/docs/resources/spo/SPO-method.png differ
diff --git a/docs/resources/spo/SPO-open_ended_task_figure.png b/docs/resources/spo/SPO-open_ended_task_figure.png
new file mode 100644
index 000000000..99d2b733d
Binary files /dev/null and b/docs/resources/spo/SPO-open_ended_task_figure.png differ
diff --git a/examples/aflow/README.md b/examples/aflow/README.md
new file mode 100644
index 000000000..332cc4b3d
--- /dev/null
+++ b/examples/aflow/README.md
@@ -0,0 +1,88 @@
+# AFlow: Automating Agentic Workflow Generation
+
+AFlow is a framework for automatically generating and optimizing Agentic Workflows. It uses Monte Carlo tree search in a code-represented workflow space to find effective workflows, replacing manual development with machine effort. Our approach shows potential to outperform handcrafted workflows on various tasks.
+
+[Read our paper on arXiv](https://arxiv.org/abs/2410.10762)
+
+
+
+
+
+## Framework Components
+
+- **Node**: Basic unit of LLM invocation. See `metagpt/actions/action_node.py` for a flexible interface to control LLM, temperature, format, and prompt.
+- **Operator**: Predefined combinations of Nodes to enhance search efficiency. Encapsulates common operations like Generate, Format, Review, Revise, Ensemble, Test, and Programmer. See `metagpt/ext/aflow/operator.py` for details. You can customize your own Operator by referencing the implementations in this code.
+- **Workflow**: A sequence of LLM-invoking nodes connected by edges. Can be represented as graphs, neural networks, or code to express various execution structures. See `metagpt/ext/aflow/workflow.py` for our implementation.
+- **Optimizer**: Uses LLMs within a Monte Carlo Tree Search variant to explore and refine workflows. Iteratively selects, expands, evaluates, and updates workflows based on performance. See `metagpt/ext/aflow/scripts/optimizer.py` for details.
+- **Evaluator**: Assesses workflow performance on given tasks. Provides feedback to guide the optimization process towards more effective workflows. See `metagpt/ext/aflow/scripts/evaluator.py` for details.
+
+
+
+
+
+## Datasets
+
+### Experimental Datasets
+We conducted experiments on six datasets (HumanEval, MBPP, GSM8K, MATH, HotpotQA, DROP) and provide their evaluation code. The data can be found in this [datasets](https://drive.google.com/uc?export=download&id=1DNoegtZiUhWtvkd2xoIuElmIi4ah7k8e) link, or you can download them using `metagpt/ext/aflow/data/download_data.py`
+
+
+
+*SPO demonstrates superior cost efficiency, requiring only 1.1% to 5.6% of the cost of state-of-the-art methods while maintaining competitive performance.*
+
+### Open-ended Tasks
+
+
+
+
+*SPO significantly improves model performance across all model configurations in open-ended tasks.*
+
+## 🚀 Quick Start
+
+### 1. Configure Your API Key ⚙️
+
+Configure LLM parameters in `config/config2.yaml` (see `examples/spo/config2.example.yaml` for reference)
+### 2. Define Your Iteration template 📝
+
+Create a Iteration template file `metagpt/ext/spo/settings/task_name.yaml`:
+```yaml
+prompt: |
+ Please solve the following problem.
+
+requirements: |
+ ...
+
+count: None
+
+qa:
+ - question: |
+ ...
+ answer: |
+ ...
+
+ - question: |
+ ...
+ answer: |
+ ...
+```
+
+Notes:
+- `prompt`: Initial prompt for iteration
+- `requirements`: Desired effects/outcomes (e.g., generate more thinking, use more humorous language)
+- `count`: Target word count for the generated prompt (e.g., 50). Set to None for no limit
+- `faq`: QA pairs used for iteration, can include appropriate number of pairs (typically 3)
+ - `question`: Questions from the dataset used for iteration
+ - `answer`: Corresponding answers. Can contain desired thinking patterns or responses instead of actual answers, or can be left empty. See `metagpt/ext/spo/settings/Navigate.yaml` for reference
+
+### 3. Implement the PromptOptimizer 🔧
+
+You have three ways to run the PromptOptimizer:
+
+#### Option 1: Python Script
+
+```python
+from metagpt.ext.spo.components.optimizer import PromptOptimizer
+from metagpt.ext.spo.utils.llm_client import SPO_LLM
+
+if __name__ == "__main__":
+ # Initialize LLM settings
+ SPO_LLM.initialize(
+ optimize_kwargs={"model": "claude-3-5-sonnet-20240620", "temperature": 0.7},
+ evaluate_kwargs={"model": "gpt-4o-mini", "temperature": 0.3},
+ execute_kwargs={"model": "gpt-4o-mini", "temperature": 0}
+ )
+
+ # Create and run optimizer
+ optimizer = PromptOptimizer(
+ optimized_path="workspace", # Output directory
+ initial_round=1, # Starting round
+ max_rounds=10, # Maximum optimization rounds
+ template="Poem.yaml", # Template file
+ name="Poem", # Project name
+ )
+
+ optimizer.optimize()
+```
+
+#### Option 2: Command Line Interface
+
+```bash
+python -m examples.spo.optimize
+```
+
+Available command line options:
+```
+--opt-model Model for optimization (default: claude-3-5-sonnet-20240620)
+--opt-temp Temperature for optimization (default: 0.7)
+--eval-model Model for evaluation (default: gpt-4o-mini)
+--eval-temp Temperature for evaluation (default: 0.3)
+--exec-model Model for execution (default: gpt-4o-mini)
+--exec-temp Temperature for execution (default: 0)
+--workspace Output directory path (default: workspace)
+--initial-round Initial round number (default: 1)
+--max-rounds Maximum number of rounds (default: 10)
+--template Template file name (default: Poem.yaml)
+--name Project name (default: Poem)
+```
+
+For help:
+```bash
+python -m examples.spo.optimize --help
+```
+
+#### Option 3: Streamlit Web Interface
+
+For a more user-friendly experience, you can use the Streamlit web interface to configure and run the optimizer.
+
+First, install Streamlit:
+```bash
+pip install "streamlit~=1.42.0"
+```
+
+Then run the web interface:
+```bash
+python -m streamlit run metagpt/ext/spo/app.py
+```
+
+### 4. View Results
+```
+workspace
+ └── Project_name
+ └── prompts
+ ├── results.json
+ ├── round_1
+ │ ├── answers.txt
+ │ └── prompt.txt
+ ├── round_2
+ │ ├── answers.txt
+ │ └── prompt.txt
+ ├── round_3
+ │ ├── answers.txt
+ │ └── prompt.txt
+ ├── ...
+ └── round_n
+ ├── answers.txt
+ └── prompt.txt
+```
+
+- `results.json`: Stores whether each iteration round was judged successful and other related information
+- `prompt.txt`: The optimized prompt for the corresponding round
+- `answers.txt`: The output results generated using the prompt for the corresponding round
+
+## Citation
+
+If you use SPO in your research, please cite our paper:
+
+```
+@misc{xiang2025spo,
+ title={Self-Supervised Prompt Optimization},
+ author={Jinyu Xiang and Jiayi Zhang and Zhaoyang Yu and Fengwei Teng and Jinhao Tu and Xinbing Liang and Sirui Hong and Chenglin Wu and Yuyu Luo},
+ year={2025},
+ eprint={2502.06855},
+ archivePrefix={arXiv},
+ primaryClass={cs.CL},
+ url={https://arxiv.org/abs/2502.06855},
+}
+```
\ No newline at end of file
diff --git a/examples/spo/config2.example.yaml b/examples/spo/config2.example.yaml
new file mode 100644
index 000000000..a19d6e815
--- /dev/null
+++ b/examples/spo/config2.example.yaml
@@ -0,0 +1,25 @@
+llm:
+ api_type: "openai"
+ model: "gpt-4o-mini"
+ base_url: ""
+ api_key: ""
+ temperature: 0
+models:
+ "gpt-4o": # model: "gpt-4-turbo" # or gpt-3.5-turbo
+ api_type: "openai" # or azure / ollama / groq etc.
+ base_url: ""
+ api_key: ""
+ temperature: 0
+ "deepseek-chat": # api_type: "openai" # or azure / ollama / groq etc.
+ api_type: "openai" # or azure / ollama / groq etc.
+ base_url: ""
+ api_key: ""
+ temperature: 0
+ "gpt-4o-mini": # api_type: "openai" # or azure / ollama / groq etc.
+ api_type: "openai" # or azure / ollama / groq etc.
+ base_url: ""
+ api_key: ""
+ temperature: 0
+
+# Other models
+
diff --git a/examples/spo/optimize.py b/examples/spo/optimize.py
new file mode 100644
index 000000000..0f11f043a
--- /dev/null
+++ b/examples/spo/optimize.py
@@ -0,0 +1,49 @@
+import argparse
+
+from metagpt.ext.spo.components.optimizer import PromptOptimizer
+from metagpt.ext.spo.utils.llm_client import SPO_LLM
+
+
+def parse_args():
+ parser = argparse.ArgumentParser(description="SPO PromptOptimizer CLI")
+
+ # LLM parameter
+ parser.add_argument("--opt-model", type=str, default="claude-3-5-sonnet-20240620", help="Model for optimization")
+ parser.add_argument("--opt-temp", type=float, default=0.7, help="Temperature for optimization")
+ parser.add_argument("--eval-model", type=str, default="gpt-4o-mini", help="Model for evaluation")
+ parser.add_argument("--eval-temp", type=float, default=0.3, help="Temperature for evaluation")
+ parser.add_argument("--exec-model", type=str, default="gpt-4o-mini", help="Model for execution")
+ parser.add_argument("--exec-temp", type=float, default=0, help="Temperature for execution")
+
+ # PromptOptimizer parameter
+ parser.add_argument("--workspace", type=str, default="workspace", help="Path for optimized output")
+ parser.add_argument("--initial-round", type=int, default=1, help="Initial round number")
+ parser.add_argument("--max-rounds", type=int, default=10, help="Maximum number of rounds")
+ parser.add_argument("--template", type=str, default="Poem.yaml", help="Template file name")
+ parser.add_argument("--name", type=str, default="Poem", help="Project name")
+
+ return parser.parse_args()
+
+
+def main():
+ args = parse_args()
+
+ SPO_LLM.initialize(
+ optimize_kwargs={"model": args.opt_model, "temperature": args.opt_temp},
+ evaluate_kwargs={"model": args.eval_model, "temperature": args.eval_temp},
+ execute_kwargs={"model": args.exec_model, "temperature": args.exec_temp},
+ )
+
+ optimizer = PromptOptimizer(
+ optimized_path=args.workspace,
+ initial_round=args.initial_round,
+ max_rounds=args.max_rounds,
+ template=args.template,
+ name=args.name,
+ )
+
+ optimizer.optimize()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/examples/stanford_town/run_st_game.py b/examples/stanford_town/run_st_game.py
index 732ae5efc..1a2d50f21 100644
--- a/examples/stanford_town/run_st_game.py
+++ b/examples/stanford_town/run_st_game.py
@@ -1,6 +1,7 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Desc : entry of Stanford Town(ST/st) game
+# README see `metagpt/ext/stanford_town/README.md`
import asyncio
from typing import Optional
diff --git a/examples/stream_output_via_api.py b/examples/stream_output_via_api.py
new file mode 100644
index 000000000..5961f3a08
--- /dev/null
+++ b/examples/stream_output_via_api.py
@@ -0,0 +1,117 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+"""
+@Time : 2024/3/27 9:44
+@Author : leiwu30
+@File : stream_output_via_api.py
+@Description : Stream log information and communicate over the network via web api.
+"""
+import asyncio
+import json
+import socket
+import threading
+from contextvars import ContextVar
+
+from flask import Flask, Response, jsonify, request, send_from_directory
+
+from metagpt.const import TUTORIAL_PATH
+from metagpt.logs import logger, set_llm_stream_logfunc
+from metagpt.roles.tutorial_assistant import TutorialAssistant
+from metagpt.utils.stream_pipe import StreamPipe
+
+app = Flask(__name__)
+
+
+def stream_pipe_log(content):
+ print(content, end="")
+ stream_pipe = stream_pipe_var.get(None)
+ if stream_pipe:
+ stream_pipe.set_message(content)
+
+
+def write_tutorial(message):
+ async def main(idea, stream_pipe):
+ stream_pipe_var.set(stream_pipe)
+ role = TutorialAssistant()
+ await role.run(idea)
+
+ def thread_run(idea: str, stream_pipe: StreamPipe = None):
+ """
+ Convert asynchronous function to thread function
+ """
+ asyncio.run(main(idea, stream_pipe))
+
+ stream_pipe = StreamPipe()
+ thread = threading.Thread(
+ target=thread_run,
+ args=(
+ message["content"],
+ stream_pipe,
+ ),
+ )
+ thread.start()
+
+ while thread.is_alive():
+ msg = stream_pipe.get_message()
+ yield stream_pipe.msg2stream(msg)
+
+
+@app.route("/v1/chat/completions", methods=["POST"])
+def completions():
+ """
+ data: {
+ "model": "write_tutorial",
+ "stream": true,
+ "messages": [
+ {
+ "role": "user",
+ "content": "Write a tutorial about MySQL"
+ }
+ ]
+ }
+ """
+
+ data = json.loads(request.data)
+ logger.info(json.dumps(data, indent=4, ensure_ascii=False))
+
+ # Non-streaming interfaces are not supported yet
+ stream_type = True if data.get("stream") else False
+ if not stream_type:
+ return jsonify({"status": 400, "msg": "Non-streaming requests are not supported, please use `stream=True`."})
+
+ # Only accept the last user information
+ # openai['model'] ~ MetaGPT['agent']
+ last_message = data["messages"][-1]
+ model = data["model"]
+
+ # write_tutorial
+ if model == "write_tutorial":
+ return Response(write_tutorial(last_message), mimetype="text/plain")
+ else:
+ return jsonify({"status": 400, "msg": "No suitable agent found."})
+
+
+@app.route("/download/")
+def download_file(filename):
+ return send_from_directory(TUTORIAL_PATH, filename, as_attachment=True)
+
+
+if __name__ == "__main__":
+ """
+ curl https://$server_address:$server_port/v1/chat/completions -X POST -d '{
+ "model": "write_tutorial",
+ "stream": true,
+ "messages": [
+ {
+ "role": "user",
+ "content": "Write a tutorial about MySQL"
+ }
+ ]
+ }'
+ """
+ server_port = 7860
+ server_address = socket.gethostbyname(socket.gethostname())
+
+ set_llm_stream_logfunc(stream_pipe_log)
+ stream_pipe_var: ContextVar[StreamPipe] = ContextVar("stream_pipe")
+ app.run(port=server_port, host=server_address)
diff --git a/examples/ui_with_chainlit/.gitignore b/examples/ui_with_chainlit/.gitignore
new file mode 100644
index 000000000..1e528c384
--- /dev/null
+++ b/examples/ui_with_chainlit/.gitignore
@@ -0,0 +1,3 @@
+*.chainlit
+chainlit.md
+.files
\ No newline at end of file
diff --git a/examples/ui_with_chainlit/README.md b/examples/ui_with_chainlit/README.md
new file mode 100644
index 000000000..0ad466162
--- /dev/null
+++ b/examples/ui_with_chainlit/README.md
@@ -0,0 +1,34 @@
+# MetaGPT in UI with Chainlit! 🤖
+
+- MetaGPT functionality in UI using Chainlit.
+- It also takes a **one line requirement** as input and outputs **user stories / competitive analysis / requirements / data structures / APIs / documents, etc.**, But `everything in UI`.
+
+## Install Chainlit
+
+- Setup initial MetaGPT config from [Main](../../README.md).
+
+```bash
+pip install chainlit
+```
+
+## Usage
+
+```bash
+chainlit run app.py
+```
+
+- Now go to: http://localhost:8000
+
+- Select,
+ - `Create a 2048 game`
+ - `Write a cli Blackjack Game`
+ - `Type your own message...`
+
+- It will run a metagpt software company.
+
+## To Setup with own application
+
+- We can change `Environment.run`, `Team.run`, `Role.run`, `Role._act`, `Action.run`.
+- In this code, changed `Environment.run`, as it was easier to do.
+- We will need to change `metagpt.logs.set_llm_stream_logfunc` to stream messages in UI with Chainlit Message.
+- To use at some other place we need to call `chainlit.Message(content="").send()` with content.
\ No newline at end of file
diff --git a/examples/ui_with_chainlit/__init__.py b/examples/ui_with_chainlit/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/examples/ui_with_chainlit/app.py b/examples/ui_with_chainlit/app.py
new file mode 100644
index 000000000..3b449a12c
--- /dev/null
+++ b/examples/ui_with_chainlit/app.py
@@ -0,0 +1,83 @@
+import chainlit as cl
+from init_setup import ChainlitEnv
+
+from metagpt.roles import (
+ Architect,
+ Engineer,
+ ProductManager,
+ ProjectManager,
+ QaEngineer,
+)
+from metagpt.team import Team
+
+
+# https://docs.chainlit.io/concepts/starters
+@cl.set_chat_profiles
+async def chat_profile() -> list[cl.ChatProfile]:
+ """Generates a chat profile containing starter messages which can be triggered to run MetaGPT
+
+ Returns:
+ list[chainlit.ChatProfile]: List of Chat Profile
+ """
+ return [
+ cl.ChatProfile(
+ name="MetaGPT",
+ icon="/public/MetaGPT-new-log.jpg",
+ markdown_description="It takes a **one line requirement** as input and outputs **user stories / competitive analysis / requirements / data structures / APIs / documents, etc.**, But `everything in UI`.",
+ starters=[
+ cl.Starter(
+ label="Create a 2048 Game",
+ message="Create a 2048 game",
+ icon="/public/2048.jpg",
+ ),
+ cl.Starter(
+ label="Write a cli Blackjack Game",
+ message="Write a cli Blackjack Game",
+ icon="/public/blackjack.jpg",
+ ),
+ ],
+ )
+ ]
+
+
+# https://docs.chainlit.io/concepts/message
+@cl.on_message
+async def startup(message: cl.Message) -> None:
+ """On Message in UI, Create a MetaGPT software company
+
+ Args:
+ message (chainlit.Message): message by chainlist
+ """
+ idea = message.content
+ company = Team(env=ChainlitEnv())
+
+ # Similar to software_company.py
+ company.hire(
+ [
+ ProductManager(),
+ Architect(),
+ ProjectManager(),
+ Engineer(n_borg=5, use_code_review=True),
+ QaEngineer(),
+ ]
+ )
+
+ company.invest(investment=3.0)
+ company.run_project(idea=idea)
+
+ await company.run(n_round=5)
+
+ workdir = company.env.context.git_repo.workdir
+ files = company.env.context.git_repo.get_files(workdir)
+ files = "\n".join([f"{workdir}/{file}" for file in files if not file.startswith(".git")])
+
+ await cl.Message(
+ content=f"""
+Codes can be found here:
+{files}
+
+---
+
+Total cost: `{company.cost_manager.total_cost}`
+"""
+ ).send()
diff --git a/examples/ui_with_chainlit/init_setup.py b/examples/ui_with_chainlit/init_setup.py
new file mode 100644
index 000000000..2b00fe465
--- /dev/null
+++ b/examples/ui_with_chainlit/init_setup.py
@@ -0,0 +1,69 @@
+import asyncio
+
+import chainlit as cl
+
+from metagpt.environment import Environment
+from metagpt.logs import logger, set_llm_stream_logfunc
+from metagpt.roles import Role
+from metagpt.utils.common import any_to_name
+
+
+def log_llm_stream_chainlit(msg):
+ # Stream the message token into Chainlit UI.
+ cl.run_sync(chainlit_message.stream_token(msg))
+
+
+set_llm_stream_logfunc(func=log_llm_stream_chainlit)
+
+
+class ChainlitEnv(Environment):
+ """Chainlit Environment for UI Integration"""
+
+ async def run(self, k=1):
+ """处理一次所有信息的运行
+ Process all Role runs at once
+ """
+ for _ in range(k):
+ futures = []
+ for role in self.roles.values():
+ # Call role.run with chainlit configuration
+ future = self._chainlit_role_run(role=role)
+ futures.append(future)
+
+ await asyncio.gather(*futures)
+ logger.debug(f"is idle: {self.is_idle}")
+
+ async def _chainlit_role_run(self, role: Role) -> None:
+ """To run the role with chainlit config
+
+ Args:
+ role (Role): metagpt.role.Role
+ """
+ global chainlit_message
+ chainlit_message = cl.Message(content="")
+
+ message = await role.run()
+ # If message is from role._act() publish to UI.
+ if message is not None and message.content != "No actions taken yet":
+ # Convert a message from action node in json format
+ chainlit_message.content = await self._convert_message_to_markdownjson(message=chainlit_message.content)
+
+ # message content from which role and its action...
+ chainlit_message.content += f"---\n\nAction: `{any_to_name(message.cause_by)}` done by `{role._setting}`."
+
+ await chainlit_message.send()
+
+ # for clean view in UI
+ async def _convert_message_to_markdownjson(self, message: str) -> str:
+ """If the message is from MetaGPT Action Node output, then
+ convert it into markdown json for clear view in UI.
+
+ Args:
+ message (str): message by role._act
+
+ Returns:
+ str: message in mardown from
+ """
+ if message.startswith("[CONTENT]"):
+ return f"```json\n{message}\n```\n"
+ return message
diff --git a/examples/ui_with_chainlit/public/2048.jpg b/examples/ui_with_chainlit/public/2048.jpg
new file mode 100644
index 000000000..7042e6f63
Binary files /dev/null and b/examples/ui_with_chainlit/public/2048.jpg differ
diff --git a/examples/ui_with_chainlit/public/MetaGPT-new-log.jpg b/examples/ui_with_chainlit/public/MetaGPT-new-log.jpg
new file mode 100644
index 000000000..f67872008
Binary files /dev/null and b/examples/ui_with_chainlit/public/MetaGPT-new-log.jpg differ
diff --git a/examples/ui_with_chainlit/public/blackjack.jpg b/examples/ui_with_chainlit/public/blackjack.jpg
new file mode 100644
index 000000000..b3a412bd4
Binary files /dev/null and b/examples/ui_with_chainlit/public/blackjack.jpg differ
diff --git a/examples/use_off_the_shelf_agent.py b/examples/use_off_the_shelf_agent.py
index 0d224a92b..34d605f1a 100644
--- a/examples/use_off_the_shelf_agent.py
+++ b/examples/use_off_the_shelf_agent.py
@@ -5,11 +5,11 @@ Author: garylin2099
"""
import asyncio
-from metagpt.logs import logger
-from metagpt.roles.product_manager import ProductManager
from metagpt.environment.mgx.mgx_env import MGXEnv
-from metagpt.schema import Message
+from metagpt.logs import logger
from metagpt.roles.di.team_leader import TeamLeader
+from metagpt.roles.product_manager import ProductManager
+from metagpt.schema import Message
async def main():
diff --git a/examples/werewolf_game/evals/eval.py b/examples/werewolf_game/evals/eval.py
new file mode 100644
index 000000000..c890773de
--- /dev/null
+++ b/examples/werewolf_game/evals/eval.py
@@ -0,0 +1,218 @@
+"""
+Filename: MetaGPT/examples/werewolf_game/evals/eval.py
+Created Date: Oct 18, 2023
+Updated Date: Oct 24, 2023
+Author: [Aria](https://github.com/ariafyy)
+Info: eval the Voting Accuracy Rate of non_werewolves and Vote Difficulity
+"""
+
+import glob
+import os
+import re
+from pathlib import Path
+
+import numpy as np
+import pandas as pd
+from tqdm import tqdm
+from utils import Utils
+
+from metagpt.const import DEFAULT_WORKSPACE_ROOT, METAGPT_ROOT
+from metagpt.environment.werewolf.const import RoleType
+
+
+class Vote:
+ """Vote Evaluation"""
+
+ def __init__(self):
+ self.OUT_PATH = DEFAULT_WORKSPACE_ROOT / "outputs"
+ os.makedirs(self.OUT_PATH, exist_ok=True)
+ self.SUB_FOLDER_LIST = ["01-10", "11-20", "21-30"]
+
+ def _get_log_fileslist(self, IN_PATH) -> list[str]:
+ files_list = []
+ for SUB_FOLDER in self.SUB_FOLDER_LIST:
+ files_list.extend(glob.glob(str(IN_PATH / SUB_FOLDER / "*.txt")))
+ return files_list
+
+ def extract_votes_from_logs(self, files_list: list):
+ for in_logfile in tqdm(files_list):
+ SUB_FOLDER = (Path(in_logfile).parent).stem
+ out_txtfile = self.OUT_PATH / "# {0}_{1}.txt".format(SUB_FOLDER, Path(in_logfile).stem)
+ Utils().pick_vote_log(in_logfile, out_txtfile)
+ votefiles_list = Utils().get_file_list(self.OUT_PATH)
+ return votefiles_list
+
+ @staticmethod
+ def parse_vote_text2chunks(text: str):
+ """
+ parse each game vote log into text chunks
+
+ one chunk example:
+ ['Player1', 'Player2', 'Player3', 'Player5', 'Player6']. Say ONLY: I vote to eliminate ...
+ Player1(Witch): 49 | I vote to eliminate Player5
+ Player2(Villager): 49 | I vote to eliminate Player5
+ Player3(Villager): 49 | I vote to eliminate Player5
+ Player5(Werewolf): 49 | I vote to eliminate Player6
+ Player6(Seer): 49 | I vote to eliminate Player5
+ """
+ pattern = re.compile(r"""\[([^\]]+)\]. Say ONLY: I vote to eliminate ...""")
+ chunks = {}
+ chunk_id = 0
+ last_end = 0
+ for match in pattern.finditer(text):
+ start = match.start()
+ chunk = text[last_end:start]
+ chunks[f"vote_{chunk_id}"] = chunk.strip()
+ last_end = match.end()
+ chunk_id += 1
+ final_chunk = text[last_end:].strip()
+ if final_chunk:
+ chunks[f"vote_{chunk_id}"] = final_chunk
+ return chunks
+
+ def _vote_rate_players(self, text: str):
+ """
+ # calculate the rate of goodteam vote werewolves
+ :example:
+
+ input:
+ ['Player1', 'Player2', 'Player3', 'Player5', 'Player6']. Say ONLY: I vote to eliminate ...
+ Player1(Witch): 49 | I vote to eliminate Player5
+ Player2(Villager): 49 | I vote to eliminate Player5
+ Player3(Villager): 49 | I vote to eliminate Player5
+ Player5(Werewolf): 49 | I vote to eliminate Player6
+ Player6(Seer): 49 | I vote to eliminate Player5
+
+ output:
+ werewolves: ['Player5']
+ non_werewolves: ['Player1', 'Player2', 'Player3', 'Player6']
+ as you can see :Player2(Villager) and Player3(Villager) vote to eliminate Player5(Werewolf)
+ :return goodteam vote rateability: 100.00%
+ """
+ pattern = re.compile(r"(\w+)\(([^\)]+)\): \d+ \| I vote to eliminate (\w+)")
+ # find all werewolves
+ werewolves = []
+ for match in pattern.finditer(text):
+ if match.group(2) == RoleType.WEREWOLF.value:
+ werewolves.append(match.group(1))
+
+ # find all non_werewolves
+ non_werewolves = []
+ for match in pattern.finditer(text):
+ if match.group(2) != RoleType.WEREWOLF.value:
+ non_werewolves.append(match.group(1))
+ num_non_werewolves = len(non_werewolves)
+
+ # count players other than werewolves made the correct votes
+ correct_votes = 0
+ for match in pattern.finditer(text):
+ if match.group(2) != RoleType.WEREWOLF.value and match.group(3) in werewolves:
+ correct_votes += 1
+
+ # cal the rateability of non_werewolves
+ rate = correct_votes / num_non_werewolves
+ good_vote_rate = round(rate, 2)
+ return {"good_vote_rate": good_vote_rate, "werewolves": werewolves, "non_werewolves": non_werewolves}
+
+ def get_goodteam_vote_rate(self, text: str) -> float:
+ goodteam_vote_rate = self._vote_rate_players(text)["good_vote_rate"]
+ return goodteam_vote_rate
+
+ def get_werewolves(self, text: str) -> list:
+ werewolves_list = self._vote_rate_players(text)["werewolves"]
+ return werewolves_list
+
+ def get_non_werewolves(self, text: str) -> list:
+ non_werewolves_list = self._vote_rate_players(text)["non_werewolves"]
+ return non_werewolves_list
+
+ def get_votewolf_difficulty(self, werewolves: list, non_werewolves: list) -> str:
+ num_living_wolfs = len(werewolves)
+ num_living_players = len(werewolves) + len(non_werewolves)
+ votewolf_difficulty = "_{0} / {1}".format(num_living_wolfs, num_living_players)
+ return votewolf_difficulty
+
+ def get_result_df(self, out_txtfile: str) -> pd.DataFrame:
+ """
+ folder: sub folders for evals
+ file: evaluation file, each file represents one game
+ votes: the number of votes, eg. vote_1 represents the first vote of this game,
+ good_vote_rate:the rateability of a good person voting against a werewolf,
+ correct_votes / the total number of players other than werewolves
+ total_votes:the total number of votes cast
+ """
+ with open(out_txtfile, "r") as out_file:
+ text = out_file.read()
+ chunks = self.parse_vote_text2chunks(text)
+ res = []
+ for k, v in chunks.items():
+ if v != "":
+ chunks_list = list(chunks.keys())
+ total_votes = len(chunks_list) - 1
+ werewolves = self.get_werewolves(v)
+ non_werewolves = self.get_non_werewolves(v)
+ good_vote_rate = self.get_goodteam_vote_rate(v)
+ votewolf_difficulty = self.get_votewolf_difficulty(werewolves, non_werewolves)
+ folder = Utils().filename_to_foldername(out_txtfile)
+ result = {
+ "folder": folder,
+ "file": Path(out_txtfile).stem + ".txt",
+ "vote_round": k,
+ "good_vote_rate": good_vote_rate,
+ "total_votes": total_votes,
+ "votewolf_difficulty": votewolf_difficulty,
+ }
+ res.append(result)
+ df = pd.DataFrame(res)
+ return df
+
+ def calc_avg_rate(self, IN_PATH) -> pd.DataFrame:
+ """
+ get avg_rate for each game
+ avg_rate : the good_rate/total number of votes in the game
+ vote1_rate: First Round Voting Accuracy Rate
+ """
+ infiles_list = self._get_log_fileslist(IN_PATH)
+ votefiles_list = self.extract_votes_from_logs(infiles_list)
+ df_list = [self._load_df_from_file(file) for file in votefiles_list]
+ combined_df = pd.concat(df_list, ignore_index=True)
+ # calculate the average good_vote_rate for each file
+ mean_rates = self._calculate_mean_rates(combined_df)
+ combined_df["avg_rate"] = combined_df["file"].map(mean_rates)
+ # calculate vote1 rate
+ vote1_rates = self._calc_vote1_rates(combined_df)
+ combined_df["vote1_rate"] = combined_df["folder"].map(vote1_rates.set_index("folder")["good_vote_rate"])
+ combined_df.loc[combined_df["vote_round"] != "vote_1", "vote1_rate"] = np.nan
+ combined_df["vote1_rate"] = combined_df["vote1_rate"].apply(self._format_rates)
+ combined_df["good_vote_rate"] = combined_df["good_vote_rate"].apply(self._format_rates)
+ combined_df["avg_rate"] = combined_df["avg_rate"].apply(self._format_rates)
+ combined_df.sort_values(["file"], ascending=True, inplace=True)
+ return combined_df
+
+ def _calc_vote1_rates(self, df):
+ df_vote1 = df[df["vote_round"] == "vote_1"]
+ vote1_rates = df_vote1.groupby("folder")["good_vote_rate"].mean().reset_index()
+ return vote1_rates
+
+ def _load_df_from_file(self, file):
+ return self.get_result_df(file)
+
+ def _calculate_mean_rates(self, df):
+ return df.groupby("file")["good_vote_rate"].mean()
+
+ def _format_rates(self, s):
+ return Utils().float_to_percent(s)
+
+ def get_eval_csv(self, IN_PATH, EVAL_RESULT):
+ """
+ IN_PATH : parent folder of ["01-10", "11-20", "21-30"]
+ EVAL_RESULT : output csv file path
+ """
+ combined_df = self.calc_avg_rate(IN_PATH)
+ combined_df.to_csv(EVAL_RESULT, index=False)
+
+
+if __name__ == "__main__":
+ IN_PATH = METAGPT_ROOT / "examples/werewolf_game/evals"
+ EVAL_RESULT = DEFAULT_WORKSPACE_ROOT / "outputs" / "goodteam_vote_rate.csv"
+ Vote().get_eval_csv(IN_PATH, EVAL_RESULT)
diff --git a/examples/werewolf_game/evals/utils.py b/examples/werewolf_game/evals/utils.py
new file mode 100644
index 000000000..490e7126f
--- /dev/null
+++ b/examples/werewolf_game/evals/utils.py
@@ -0,0 +1,134 @@
+"""
+Filename: MetaGPT/examples/werewolf_game/evals/utils.py
+Created Date: Oct 11, 2023
+Revised Date: Oct 20, 2023
+Author: [Aria](https://github.com/ariafyy)
+"""
+import glob
+import os
+import re
+from pathlib import Path
+
+from metagpt.const import METAGPT_ROOT
+
+
+class Utils:
+ """Utils: utils of logs"""
+
+ @staticmethod
+ def polish_log(in_logfile, out_txtfile):
+ """polish logs for evaluation"""
+ pattern_text = r"(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3}) \| (\w+) +\| ([\w\.]+:\w+:\d+) - (.*\S)"
+ pattern_player = r"(Player(\d{1}): \w+)"
+ pattern_start = False
+ json_start = False
+
+ with open(in_logfile, "r") as f, open(out_txtfile, "w") as out:
+ for line in f.readlines():
+ matches = re.match(pattern_text, line)
+ if matches:
+ message = matches.group(4).strip()
+ pattern_start = True
+ json_start = False
+
+ if (
+ "Moderator(Moderator) ready to InstructSpeak" not in message
+ and "Moderator(Moderator) ready to ParseSpeak" not in message
+ and "Total running cost:" not in message
+ ):
+ out.write("- " + message + "\n")
+ else:
+ out.write("\n")
+
+ elif pattern_start and not matches:
+ if "gpt-4 may update over time" in line:
+ line = ""
+ out.write(line)
+
+ elif line.strip().startswith("{"):
+ out.write(line.strip())
+ json_start = True
+
+ elif json_start and not line.strip().endswith("}"):
+ out.write(line.strip())
+
+ elif json_start and line.strip().endswith("}"):
+ out.write(line.strip())
+ json_start = False
+
+ elif (
+ line.startswith("(User):") or line.startswith("********** STEP:") or re.search(pattern_player, line)
+ ):
+ out.write(line)
+
+ else:
+ out.write("\n")
+
+ @staticmethod
+ def pick_vote_log(in_logfile, out_txtfile):
+ """
+ pick the vote log from the log file.
+ ready to AnnounceGameResult serves as the 'HINT_TEXT ' which indicates the end of the game.
+ based on bservation and reflection, then discuss is not in vote session.
+ """
+ pattern_vote = r"(Player\d+)\(([A-Za-z]+)\): (\d+) \| (I vote to eliminate Player\d+)"
+ ignore_text = """reflection"""
+ HINT_TEXT = r"ready to AnnounceGameResult"
+ pattern_moderator = r"\[([^\]]+)\]\. Say ONLY: I vote to eliminate ..."
+ in_valid_block = False
+
+ with open(in_logfile, "r") as f:
+ lines = f.read()
+ split_lines = lines.split(HINT_TEXT)
+
+ if len(split_lines) < 2:
+ print(f"Key text :{HINT_TEXT} not found in {in_logfile}")
+ return
+
+ relevant_lines = split_lines[1].split("\n")
+ with open(out_txtfile, "w") as out:
+ for line in relevant_lines:
+ if re.search(pattern_moderator, line):
+ in_valid_block = True
+ out.write(line.lstrip() + "\n")
+
+ elif in_valid_block and re.search(pattern_vote, line):
+ out.write(line + "\n")
+ elif ignore_text in line:
+ in_valid_block = False
+
+ @staticmethod
+ def get_file_list(path: str) -> list:
+ file_pattern = os.path.join(path, "*.txt")
+ files_list = glob.glob(file_pattern)
+ return files_list
+
+ @staticmethod
+ def filename_to_foldername(out_txtfile: str):
+ """
+ convert filename into its parent folder name
+ input:"....../# 01-10_10132100.txt"
+ output:# 01-10
+ """
+ s = Path(out_txtfile).stem
+ pattern_folder = r"([^_]*)_"
+ match = re.match(pattern_folder, s)
+ if match:
+ folder = match.group(1)
+ return folder
+
+ @staticmethod
+ def float_to_percent(decimal: float) -> str:
+ """
+ input: 1.00
+ output: 100.00%
+ """
+ percent = decimal * 100
+ return f"{percent:.2f}%"
+
+
+if __name__ == "__main__":
+ in_logfile = METAGPT_ROOT / "logs/log.txt"
+ out_txtfile = "input your wish path"
+ # Utils().polish_log(in_logfile, out_txtfile)
+ Utils().pick_vote_log(in_logfile, out_txtfile)
diff --git a/examples/werewolf_game/start_game.py b/examples/werewolf_game/start_game.py
new file mode 100644
index 000000000..fe31c6c55
--- /dev/null
+++ b/examples/werewolf_game/start_game.py
@@ -0,0 +1,68 @@
+import asyncio
+
+import fire
+
+from metagpt.ext.werewolf.roles import Guard, Moderator, Seer, Villager, Werewolf, Witch
+from metagpt.ext.werewolf.roles.human_player import prepare_human_player
+from metagpt.ext.werewolf.werewolf_game import WerewolfGame
+from metagpt.logs import logger
+
+
+async def start_game(
+ investment: float = 3.0,
+ n_round: int = 5,
+ shuffle: bool = True,
+ add_human: bool = False,
+ use_reflection: bool = True,
+ use_experience: bool = False,
+ use_memory_selection: bool = False,
+ new_experience_version: str = "",
+):
+ game = WerewolfGame()
+ game_setup, players = game.env.init_game_setup(
+ role_uniq_objs=[Villager, Werewolf, Guard, Seer, Witch],
+ num_werewolf=2,
+ num_villager=2,
+ shuffle=shuffle,
+ add_human=add_human,
+ use_reflection=use_reflection,
+ use_experience=use_experience,
+ use_memory_selection=use_memory_selection,
+ new_experience_version=new_experience_version,
+ prepare_human_player=prepare_human_player,
+ )
+ logger.info(f"{game_setup}")
+
+ players = [Moderator()] + players
+ game.hire(players)
+ game.invest(investment)
+ game.run_project(game_setup)
+ await game.run(n_round=n_round)
+
+
+def main(
+ investment: float = 20.0,
+ n_round: int = 100,
+ shuffle: bool = True,
+ add_human: bool = False,
+ use_reflection: bool = True,
+ use_experience: bool = False,
+ use_memory_selection: bool = False,
+ new_experience_version: str = "",
+):
+ asyncio.run(
+ start_game(
+ investment,
+ n_round,
+ shuffle,
+ add_human,
+ use_reflection,
+ use_experience,
+ use_memory_selection,
+ new_experience_version,
+ )
+ )
+
+
+if __name__ == "__main__":
+ fire.Fire(main)
diff --git a/metagpt/actions/action.py b/metagpt/actions/action.py
index 8733947f5..ba8a94803 100644
--- a/metagpt/actions/action.py
+++ b/metagpt/actions/action.py
@@ -8,12 +8,14 @@
from __future__ import annotations
-from typing import Optional, Union
+from typing import Any, Optional, Union
from pydantic import BaseModel, ConfigDict, Field, model_validator
from metagpt.actions.action_node import ActionNode
+from metagpt.configs.models_config import ModelsConfig
from metagpt.context_mixin import ContextMixin
+from metagpt.provider.llm_provider_registry import create_llm_instance
from metagpt.schema import (
CodePlanAndChangeContext,
CodeSummarizeContext,
@@ -34,6 +36,19 @@ class Action(SerializationMixin, ContextMixin, BaseModel):
prefix: str = "" # aask*时会加上prefix,作为system_message
desc: str = "" # for skill manager
node: ActionNode = Field(default=None, exclude=True)
+ # The model name or API type of LLM of the `models` in the `config2.yaml`;
+ # Using `None` to use the `llm` configuration in the `config2.yaml`.
+ llm_name_or_type: Optional[str] = None
+
+ @model_validator(mode="after")
+ @classmethod
+ def _update_private_llm(cls, data: Any) -> Any:
+ config = ModelsConfig.default().get(data.llm_name_or_type)
+ if config:
+ llm = create_llm_instance(config)
+ llm.cost_manager = data.llm.cost_manager
+ data.llm = llm
+ return data
@property
def prompt_schema(self):
diff --git a/metagpt/actions/action_node.py b/metagpt/actions/action_node.py
index c1de16656..7109f287e 100644
--- a/metagpt/actions/action_node.py
+++ b/metagpt/actions/action_node.py
@@ -9,6 +9,7 @@ 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
+import re
import typing
from enum import Enum
from typing import Any, Dict, List, Optional, Tuple, Type, Union
@@ -25,6 +26,7 @@ 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
+from metagpt.utils.sanitize import sanitize
class ReviewMode(Enum):
@@ -40,9 +42,17 @@ class ReviseMode(Enum):
TAG = "CONTENT"
+
+class FillMode(Enum):
+ CODE_FILL = "code_fill"
+ XML_FILL = "xml_fill"
+ SINGLE_FILL = "single_fill"
+
+
LANGUAGE_CONSTRAINT = "Language: Please use the same language as Human INPUT."
FORMAT_CONSTRAINT = f"Format: output wrapped inside [{TAG}][/{TAG}] like format example, nothing else."
+
SIMPLE_TEMPLATE = """
## context
{context}
@@ -239,12 +249,19 @@ class ActionNode:
"""基于pydantic v2的模型动态生成,用来检验结果类型正确性"""
def check_fields(cls, values):
- required_fields = set(mapping.keys())
+ all_fields = set(mapping.keys())
+ required_fields = set()
+ for k, v in mapping.items():
+ type_v, field_info = v
+ if ActionNode.is_optional_type(type_v):
+ continue
+ required_fields.add(k)
+
missing_fields = required_fields - set(values.keys())
if missing_fields:
raise ValueError(f"Missing fields: {missing_fields}")
- unrecognized_fields = set(values.keys()) - required_fields
+ unrecognized_fields = set(values.keys()) - all_fields
if unrecognized_fields:
logger.warning(f"Unrecognized fields: {unrecognized_fields}")
return values
@@ -452,7 +469,6 @@ class ActionNode:
self, schema, mode, images: Optional[Union[str, list[str]]] = None, timeout=USE_CONFIG_TIMEOUT, exclude=None
):
prompt = self.compile(context=self.context, schema=schema, mode=mode, exclude=exclude)
-
if schema != "raw":
mapping = self.get_mapping(mode, exclude=exclude)
class_name = f"{self.key}_AN"
@@ -467,6 +483,116 @@ class ActionNode:
return self
+ def get_field_name(self):
+ """
+ Get the field name from the Pydantic model associated with this ActionNode.
+ """
+ model_class = self.create_class()
+ fields = model_class.model_fields
+
+ # Assuming there's only one field in the model
+ if len(fields) == 1:
+ return next(iter(fields))
+
+ # If there are multiple fields, we might want to use self.key to find the right one
+ return self.key
+
+ def get_field_names(self):
+ """
+ Get the field names associated with this ActionNode's Pydantic model.
+ """
+ model_class = self.create_class()
+ return model_class.model_fields.keys()
+
+ def get_field_types(self):
+ """
+ Get the field types associated with this ActionNode's Pydantic model.
+ """
+ model_class = self.create_class()
+ return {field_name: field.annotation for field_name, field in model_class.model_fields.items()}
+
+ def xml_compile(self, context):
+ """
+ Compile the prompt to make it easier for the model to understand the xml format.
+ """
+ field_names = self.get_field_names()
+ # Construct the example using the field names
+ examples = []
+ for field_name in field_names:
+ examples.append(f"<{field_name}>content{field_name}>")
+
+ # Join all examples into a single string
+ example_str = "\n".join(examples)
+ # Add the example to the context
+ context += f"""
+### Response format (must be strictly followed): All content must be enclosed in the given XML tags, ensuring each opening has a corresponding closing , with no incomplete or self-closing tags allowed.\n
+{example_str}
+"""
+ return context
+
+ async def code_fill(
+ self, context: str, function_name: Optional[str] = None, timeout: int = USE_CONFIG_TIMEOUT
+ ) -> Dict[str, str]:
+ """
+ Fill CodeBlock Using ``` ```
+ """
+ field_name = self.get_field_name()
+ prompt = context
+ content = await self.llm.aask(prompt, timeout=timeout)
+ extracted_code = sanitize(code=content, entrypoint=function_name)
+ result = {field_name: extracted_code}
+ return result
+
+ async def single_fill(self, context: str, images: Optional[Union[str, list[str]]] = None) -> Dict[str, str]:
+ field_name = self.get_field_name()
+ prompt = context
+ content = await self.llm.aask(prompt, images=images)
+ result = {field_name: content}
+ return result
+
+ async def xml_fill(self, context: str, images: Optional[Union[str, list[str]]] = None) -> Dict[str, Any]:
+ """
+ Fill context with XML tags and convert according to field types, including string, integer, boolean, list and dict types
+ """
+ field_names = self.get_field_names()
+ field_types = self.get_field_types()
+
+ extracted_data: Dict[str, Any] = {}
+ content = await self.llm.aask(context, images=images)
+
+ for field_name in field_names:
+ pattern = rf"<{field_name}>(.*?){field_name}>"
+ match = re.search(pattern, content, re.DOTALL)
+ if match:
+ raw_value = match.group(1).strip()
+ field_type = field_types.get(field_name)
+
+ if field_type == str:
+ extracted_data[field_name] = raw_value
+ elif field_type == int:
+ try:
+ extracted_data[field_name] = int(raw_value)
+ except ValueError:
+ extracted_data[field_name] = 0 # 或者其他默认值
+ elif field_type == bool:
+ extracted_data[field_name] = raw_value.lower() in ("true", "yes", "1", "on", "True")
+ elif field_type == list:
+ try:
+ extracted_data[field_name] = eval(raw_value)
+ if not isinstance(extracted_data[field_name], list):
+ raise ValueError
+ except:
+ extracted_data[field_name] = [] # 默认空列表
+ elif field_type == dict:
+ try:
+ extracted_data[field_name] = eval(raw_value)
+ if not isinstance(extracted_data[field_name], dict):
+ raise ValueError
+ except:
+ extracted_data[field_name] = {} # 默认空字典
+
+ return extracted_data
+
@exp_cache(serializer=ActionNodeSerializer())
async def fill(
self,
@@ -479,6 +605,7 @@ class ActionNode:
images: Optional[Union[str, list[str]]] = None,
timeout=USE_CONFIG_TIMEOUT,
exclude=[],
+ function_name: str = None,
):
"""Fill the node(s) with mode.
@@ -505,6 +632,22 @@ class ActionNode:
if self.schema:
schema = self.schema
+ if mode == FillMode.CODE_FILL.value:
+ result = await self.code_fill(context, function_name, timeout)
+ self.instruct_content = self.create_class()(**result)
+ return self
+
+ elif mode == FillMode.XML_FILL.value:
+ context = self.xml_compile(context=self.context)
+ result = await self.xml_fill(context, images=images)
+ self.instruct_content = self.create_class()(**result)
+ return self
+
+ elif mode == FillMode.SINGLE_FILL.value:
+ result = await self.single_fill(context, images=images)
+ self.instruct_content = self.create_class()(**result)
+ return self
+
if strgy == "simple":
return await self.simple_fill(schema=schema, mode=mode, images=images, timeout=timeout, exclude=exclude)
elif strgy == "complex":
@@ -722,3 +865,12 @@ class ActionNode:
root_node.add_child(child_node)
return root_node
+
+ @staticmethod
+ def is_optional_type(tp) -> bool:
+ """Return True if `tp` is `typing.Optional[...]`"""
+ if typing.get_origin(tp) is Union:
+ args = typing.get_args(tp)
+ non_none_types = [arg for arg in args if arg is not type(None)]
+ return len(non_none_types) == 1 and len(args) == 2
+ return False
diff --git a/metagpt/actions/design_api_an.py b/metagpt/actions/design_api_an.py
index 0de17f32c..4707b5353 100644
--- a/metagpt/actions/design_api_an.py
+++ b/metagpt/actions/design_api_an.py
@@ -5,7 +5,7 @@
@Author : alexanderwu
@File : design_api_an.py
"""
-from typing import List
+from typing import List, Optional
from metagpt.actions.action_node import ActionNode
from metagpt.utils.mermaid import MMC1, MMC2
@@ -45,9 +45,10 @@ REFINED_FILE_LIST = ActionNode(
example=["main.py", "game.py", "new_feature.py"],
)
+# optional,because low success reproduction of class diagram in non py project.
DATA_STRUCTURES_AND_INTERFACES = ActionNode(
key="Data structures and interfaces",
- expected_type=str,
+ expected_type=Optional[str],
instruction="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.",
@@ -66,7 +67,7 @@ REFINED_DATA_STRUCTURES_AND_INTERFACES = ActionNode(
PROGRAM_CALL_FLOW = ActionNode(
key="Program call flow",
- expected_type=str,
+ expected_type=Optional[str],
instruction="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.",
example=MMC2,
diff --git a/metagpt/actions/di/write_analysis_code.py b/metagpt/actions/di/write_analysis_code.py
index 4d21b2cec..80e2c5ddc 100644
--- a/metagpt/actions/di/write_analysis_code.py
+++ b/metagpt/actions/di/write_analysis_code.py
@@ -6,8 +6,6 @@
"""
from __future__ import annotations
-import json
-
from metagpt.actions import Action
from metagpt.prompts.di.write_analysis_code import (
CHECK_DATA_PROMPT,
@@ -30,11 +28,10 @@ class WriteAnalysisCode(Action):
)
rsp = await self._aask(reflection_prompt, system_msgs=[REFLECTION_SYSTEM_MSG])
- reflection = json.loads(CodeParser.parse_code(text=rsp))
- if "```python" in reflection["improved_impl"]:
- reflection["improved_impl"] = CodeParser.parse_code(text=reflection["improved_impl"], lang="python")
-
- return reflection["improved_impl"]
+ # reflection = json.loads(CodeParser.parse_code(block=None, text=rsp))
+ # return reflection["improved_impl"]
+ reflection = CodeParser.parse_code(block=None, text=rsp)
+ return reflection
async def run(
self,
diff --git a/metagpt/actions/project_management.py b/metagpt/actions/project_management.py
index 1ce94cd99..2bfe0da3a 100644
--- a/metagpt/actions/project_management.py
+++ b/metagpt/actions/project_management.py
@@ -164,7 +164,7 @@ class WriteTasks(Action):
async def _update_requirements(self, doc):
m = json.loads(doc.content)
- packages = set(m.get("Required Python packages", set()))
+ packages = set(m.get("Required 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="")
diff --git a/metagpt/actions/project_management_an.py b/metagpt/actions/project_management_an.py
index 7131a6c99..a953feb4c 100644
--- a/metagpt/actions/project_management_an.py
+++ b/metagpt/actions/project_management_an.py
@@ -5,13 +5,13 @@
@Author : alexanderwu
@File : project_management_an.py
"""
-from typing import List
+from typing import List, Optional
from metagpt.actions.action_node import ActionNode
-REQUIRED_PYTHON_PACKAGES = ActionNode(
+REQUIRED_PACKAGES = ActionNode(
key="Required packages",
- expected_type=List[str],
+ expected_type=Optional[List[str]],
instruction="Provide required packages The response language should correspond to the context and requirements.",
example=["flask==1.1.2", "bcrypt==3.2.0"],
)
@@ -99,7 +99,7 @@ ANYTHING_UNCLEAR_PM = ActionNode(
)
NODES = [
- REQUIRED_PYTHON_PACKAGES,
+ REQUIRED_PACKAGES,
REQUIRED_OTHER_LANGUAGE_PACKAGES,
LOGIC_ANALYSIS,
TASK_LIST,
@@ -109,7 +109,7 @@ NODES = [
]
REFINED_NODES = [
- REQUIRED_PYTHON_PACKAGES,
+ REQUIRED_PACKAGES,
REQUIRED_OTHER_LANGUAGE_PACKAGES,
REFINED_LOGIC_ANALYSIS,
REFINED_TASK_LIST,
diff --git a/metagpt/actions/rebuild_sequence_view.py b/metagpt/actions/rebuild_sequence_view.py
index e23487511..627cbd151 100644
--- a/metagpt/actions/rebuild_sequence_view.py
+++ b/metagpt/actions/rebuild_sequence_view.py
@@ -478,7 +478,7 @@ class RebuildSequenceView(Action):
Returns:
List[str]: A list of participants extracted from the sequence diagram.
"""
- pattern = r"participant ([a-zA-Z\.0-9_]+)"
+ pattern = r"participant ([\w\.]+)"
matches = re.findall(pattern, mermaid_sequence_diagram)
matches = [re.sub(r"[\\/'\"]+", "", i) for i in matches]
return matches
diff --git a/metagpt/actions/research.py b/metagpt/actions/research.py
index 99f72b076..266584485 100644
--- a/metagpt/actions/research.py
+++ b/metagpt/actions/research.py
@@ -166,6 +166,8 @@ class CollectLinks(Action):
"""
max_results = max_num_results or max(num_results * 2, 6)
results = await self._search_urls(query, max_results=max_results)
+ if len(results) == 0:
+ return []
_results = "\n".join(f"{i}: {j}" for i, j in zip(range(max_results), results))
time_stamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
prompt = COLLECT_AND_RANKURLS_PROMPT.format(topic=topic, query=query, results=_results, time_stamp=time_stamp)
diff --git a/metagpt/actions/write_code_an_draft.py b/metagpt/actions/write_code_an_draft.py
index 4c3fd4c19..d6622284d 100644
--- a/metagpt/actions/write_code_an_draft.py
+++ b/metagpt/actions/write_code_an_draft.py
@@ -139,7 +139,7 @@ Language: Please use the same language as the user requirement, but the title an
end", "Anything UNCLEAR": "目前项目要求明确,没有不清楚的地方。"}
## Tasks
-{"Required Python packages": ["无需Python包"], "Required Other language third-party packages": ["vue.js"], "Logic Analysis": [["index.html", "作为游戏的入口文件和主要的HTML结构"], ["styles.css", "包含所有的CSS样式,确保游戏界面美观"], ["main.js", "包含Main类,负责初始化游戏和绑定事件"], ["game.js", "包含Game类,负责游戏逻辑,如开始游戏、移动方块等"], ["storage.js", "包含Storage类,用于获取和设置玩家的最高分"]], "Task list": ["index.html", "styles.css", "storage.js", "game.js", "main.js"], "Full API spec": "", "Shared Knowledge": "\'game.js\' 包含游戏逻辑相关的函数,被 \'main.js\' 调用。", "Anything UNCLEAR": "目前项目要求明确,没有不清楚的地方。"}
+{"Required packages": ["无需第三方包"], "Required Other language third-party packages": ["vue.js"], "Logic Analysis": [["index.html", "作为游戏的入口文件和主要的HTML结构"], ["styles.css", "包含所有的CSS样式,确保游戏界面美观"], ["main.js", "包含Main类,负责初始化游戏和绑定事件"], ["game.js", "包含Game类,负责游戏逻辑,如开始游戏、移动方块等"], ["storage.js", "包含Storage类,用于获取和设置玩家的最高分"]], "Task list": ["index.html", "styles.css", "storage.js", "game.js", "main.js"], "Full API spec": "", "Shared Knowledge": "\'game.js\' 包含游戏逻辑相关的函数,被 \'main.js\' 调用。", "Anything UNCLEAR": "目前项目要求明确,没有不清楚的地方。"}
## Code Files
----- index.html
diff --git a/metagpt/actions/write_code_review.py b/metagpt/actions/write_code_review.py
index 6a283f812..209e4e8ac 100644
--- a/metagpt/actions/write_code_review.py
+++ b/metagpt/actions/write_code_review.py
@@ -33,6 +33,8 @@ ATTENTION: Use '##' to SPLIT SECTIONS, not '#'. Output format carefully referenc
# Context
{context}
+-----
+
## Code to be Reviewed: {filename}
```Code
{code}
@@ -44,7 +46,8 @@ EXAMPLE_AND_INSTRUCTION = """
{format_example}
-# Instruction: Based on the actual code situation, follow one of the "Format example". Return only 1 file under review.
+# Instruction: Based on the actual code, follow one of the "Code Review Format example".
+- Note the code filename should be `{filename}`. Return the only ONE file `{filename}` under review.
## 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.
1. Is the code implemented as per the requirements? If not, how to achieve it? Analyse it step by step.
@@ -62,7 +65,9 @@ LGTM/LBTM
"""
FORMAT_EXAMPLE = """
-# Format example 1
+-----
+
+# Code Review Format example 1
## Code Review: {filename}
1. No, we should fix the logic of class A due to ...
2. ...
@@ -98,7 +103,9 @@ FORMAT_EXAMPLE = """
## Code Review Result
LBTM
-# Format example 2
+-----
+
+# Code Review Format example 2
## Code Review: {filename}
1. Yes.
2. Yes.
@@ -112,10 +119,12 @@ pass
## Code Review Result
LGTM
+
+-----
"""
REWRITE_CODE_TEMPLATE = """
-# Instruction: rewrite code based on the Code Review and Actions
+# Instruction: rewrite the `{filename}` based on the Code Review and Actions
## Rewrite Code: CodeBlock. If it still has some bugs, rewrite {filename} using a Markdown code block, with the filename docstring preceding the code block. Do your utmost to optimize THIS SINGLE FILE. Return all completed codes and prohibit the return of unfinished codes.
```python
## {filename}
@@ -189,6 +198,7 @@ class WriteCodeReview(Action):
)
cr_prompt = EXAMPLE_AND_INSTRUCTION.format(
format_example=format_example,
+ filename=self.i_context.code_doc.filename,
)
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
diff --git a/metagpt/config2.py b/metagpt/config2.py
index fd0cb0948..1942e5ef5 100644
--- a/metagpt/config2.py
+++ b/metagpt/config2.py
@@ -55,7 +55,10 @@ class Config(CLIParams, YamlModel):
# RAG Embedding
embedding: EmbeddingConfig = EmbeddingConfig()
- # Global Proxy. Not used by LLM, but by other tools such as browsers.
+ # omniparse
+ omniparse: OmniParseConfig = OmniParseConfig()
+
+ # Global Proxy. Will be used if llm.proxy is not set
proxy: str = ""
# Tool Parameters
@@ -86,6 +89,7 @@ class Config(CLIParams, YamlModel):
iflytek_api_key: str = ""
azure_tts_subscription_key: str = ""
azure_tts_region: str = ""
+ _extra: dict = dict() # extra config dict
# Role's custom configuration
roles: Optional[List[RoleCustomConfig]] = None
@@ -93,8 +97,6 @@ class Config(CLIParams, YamlModel):
# RoleZero's configuration
role_zero: RoleZeroConfig = Field(default_factory=RoleZeroConfig)
- omniparse: Optional[OmniParseConfig] = None
-
@classmethod
def from_home(cls, path):
"""Load config from ~/.metagpt/config2.yaml"""
@@ -104,7 +106,7 @@ class Config(CLIParams, YamlModel):
return Config.from_yaml_file(pathname)
@classmethod
- def default(cls, reload: bool = False, **kwargs):
+ def default(cls, reload: bool = False, **kwargs) -> "Config":
"""Load default config
- Priority: env < default_config_paths
- Inside default_config_paths, the latter one overwrites the former one
@@ -146,6 +148,14 @@ class Config(CLIParams, YamlModel):
self.reqa_file = reqa_file
self.max_auto_summarize_code = max_auto_summarize_code
+ @property
+ def extra(self):
+ return self._extra
+
+ @extra.setter
+ def extra(self, value: dict):
+ self._extra = value
+
def get_openai_llm(self) -> Optional[LLMConfig]:
"""Get OpenAI LLMConfig by name. If no OpenAI, raise Exception"""
if self.llm.api_type == LLMType.OPENAI:
diff --git a/metagpt/configs/llm_config.py b/metagpt/configs/llm_config.py
index 57913956c..9e27d76a6 100644
--- a/metagpt/configs/llm_config.py
+++ b/metagpt/configs/llm_config.py
@@ -5,13 +5,14 @@
@Author : alexanderwu
@File : llm_config.py
"""
+
from enum import Enum
from typing import Optional
from pydantic import field_validator
from metagpt.configs.compress_msg_config import CompressType
-from metagpt.const import LLM_API_TIMEOUT
+from metagpt.const import CONFIG_ROOT, LLM_API_TIMEOUT, METAGPT_ROOT
from metagpt.utils.yaml_model import YamlModel
@@ -26,7 +27,10 @@ class LLMType(Enum):
GEMINI = "gemini"
METAGPT = "metagpt"
AZURE = "azure"
- OLLAMA = "ollama"
+ OLLAMA = "ollama" # /chat at ollama api
+ OLLAMA_GENERATE = "ollama.generate" # /generate at ollama api
+ OLLAMA_EMBEDDINGS = "ollama.embeddings" # /embeddings at ollama api
+ OLLAMA_EMBED = "ollama.embed" # /embed at ollama api
QIANFAN = "qianfan" # Baidu BCE
DASHSCOPE = "dashscope" # Aliyun LingJi DashScope
MOONSHOT = "moonshot"
@@ -35,6 +39,9 @@ class LLMType(Enum):
OPEN_ROUTER = "open_router"
DEEPSEEK = "deepseek"
SILICONFLOW = "siliconflow"
+ OPENROUTER = "openrouter"
+ BEDROCK = "bedrock"
+ ARK = "ark" # https://www.volcengine.com/docs/82379/1263482#python-sdk
def __missing__(self, key):
return self.OPENAI
@@ -58,6 +65,7 @@ class LLMConfig(YamlModel):
# For Cloud Service Provider like Baidu/ Alibaba
access_key: Optional[str] = None
secret_key: Optional[str] = None
+ session_token: Optional[str] = None
endpoint: Optional[str] = None # for self-deployed model on the cloud
# For Spark(Xunfei), maybe remove later
@@ -76,10 +84,16 @@ class LLMConfig(YamlModel):
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
+ stream: bool = True
+ seed: Optional[int] = None
+ # https://cookbook.openai.com/examples/using_logprobs
+ logprobs: Optional[bool] = None
top_logprobs: Optional[int] = None
timeout: int = 600
+ context_length: Optional[int] = None # Max input tokens
+
+ # For Amazon Bedrock
+ region_name: str = None
# For Network
proxy: Optional[str] = None
@@ -90,11 +104,24 @@ class LLMConfig(YamlModel):
# Compress request messages under token limit
compress_type: CompressType = CompressType.NO_COMPRESS
+ # For Messages Control
+ use_system_prompt: 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 config2.yaml")
+ repo_config_path = METAGPT_ROOT / "config/config2.yaml"
+ root_config_path = CONFIG_ROOT / "config2.yaml"
+ if root_config_path.exists():
+ raise ValueError(
+ f"Please set your API key in {root_config_path}. If you also set your config in {repo_config_path}, \n"
+ f"the former will overwrite the latter. This may cause unexpected result.\n"
+ )
+ elif repo_config_path.exists():
+ raise ValueError(f"Please set your API key in {repo_config_path}")
+ else:
+ raise ValueError("Please set your API key in config2.yaml")
return v
@field_validator("timeout")
diff --git a/metagpt/configs/models_config.py b/metagpt/configs/models_config.py
new file mode 100644
index 000000000..bc4897fec
--- /dev/null
+++ b/metagpt/configs/models_config.py
@@ -0,0 +1,112 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+"""
+models_config.py
+
+This module defines the ModelsConfig class for handling configuration of LLM models.
+
+Attributes:
+ CONFIG_ROOT (Path): Root path for configuration files.
+ METAGPT_ROOT (Path): Root path for MetaGPT files.
+
+Classes:
+ ModelsConfig (YamlModel): Configuration class for LLM models.
+"""
+from pathlib import Path
+from typing import Dict, List, Optional
+
+from pydantic import Field, field_validator
+
+from metagpt.config2 import merge_dict
+from metagpt.configs.llm_config import LLMConfig
+from metagpt.const import CONFIG_ROOT, METAGPT_ROOT
+from metagpt.utils.yaml_model import YamlModel
+
+
+class ModelsConfig(YamlModel):
+ """
+ Configuration class for `models` in `config2.yaml`.
+
+ Attributes:
+ models (Dict[str, LLMConfig]): Dictionary mapping model names or types to LLMConfig objects.
+
+ Methods:
+ update_llm_model(cls, value): Validates and updates LLM model configurations.
+ from_home(cls, path): Loads configuration from ~/.metagpt/config2.yaml.
+ default(cls): Loads default configuration from predefined paths.
+ get(self, name_or_type: str) -> Optional[LLMConfig]: Retrieves LLMConfig by name or API type.
+ """
+
+ models: Dict[str, LLMConfig] = Field(default_factory=dict)
+
+ @field_validator("models", mode="before")
+ @classmethod
+ def update_llm_model(cls, value):
+ """
+ Validates and updates LLM model configurations.
+
+ Args:
+ value (Dict[str, Union[LLMConfig, dict]]): Dictionary of LLM configurations.
+
+ Returns:
+ Dict[str, Union[LLMConfig, dict]]: Updated dictionary of LLM configurations.
+ """
+ for key, config in value.items():
+ if isinstance(config, LLMConfig):
+ config.model = config.model or key
+ elif isinstance(config, dict):
+ config["model"] = config.get("model") or key
+ return value
+
+ @classmethod
+ def from_home(cls, path):
+ """
+ Loads configuration from ~/.metagpt/config2.yaml.
+
+ Args:
+ path (str): Relative path to configuration file.
+
+ Returns:
+ Optional[ModelsConfig]: Loaded ModelsConfig object or None if file doesn't exist.
+ """
+ pathname = CONFIG_ROOT / path
+ if not pathname.exists():
+ return None
+ return ModelsConfig.from_yaml_file(pathname)
+
+ @classmethod
+ def default(cls):
+ """
+ Loads default configuration from predefined paths.
+
+ Returns:
+ ModelsConfig: Default ModelsConfig object.
+ """
+ default_config_paths: List[Path] = [
+ METAGPT_ROOT / "config/config2.yaml",
+ CONFIG_ROOT / "config2.yaml",
+ ]
+
+ dicts = [ModelsConfig.read_yaml(path) for path in default_config_paths]
+ final = merge_dict(dicts)
+ return ModelsConfig(**final)
+
+ def get(self, name_or_type: str) -> Optional[LLMConfig]:
+ """
+ Retrieves LLMConfig object by name or API type.
+
+ Args:
+ name_or_type (str): Name or API type of the LLM model.
+
+ Returns:
+ Optional[LLMConfig]: LLMConfig object if found, otherwise None.
+ """
+ if not name_or_type:
+ return None
+ model = self.models.get(name_or_type)
+ if model:
+ return model
+ for m in self.models.values():
+ if m.api_type == name_or_type:
+ return m
+ return None
diff --git a/metagpt/configs/omniparse_config.py b/metagpt/configs/omniparse_config.py
index ecae78697..8f38f9f51 100644
--- a/metagpt/configs/omniparse_config.py
+++ b/metagpt/configs/omniparse_config.py
@@ -2,5 +2,6 @@ from metagpt.utils.yaml_model import YamlModel
class OmniParseConfig(YamlModel):
- url: str = ""
+ api_key: str = ""
+ base_url: str = ""
timeout: int = 600
diff --git a/metagpt/const.py b/metagpt/const.py
index b47875ff7..94a7d8529 100644
--- a/metagpt/const.py
+++ b/metagpt/const.py
@@ -1,14 +1,6 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
-"""
-@Time : 2023/5/1 11:59
-@Author : alexanderwu
-@File : const.py
-@Modified By: mashenquan, 2023-11-1. According to Section 2.2.1 and 2.2.2 of RFC 116, added key definitions for
- common properties in the Message.
-@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 os
from pathlib import Path
@@ -51,6 +43,8 @@ DEFAULT_WORKSPACE_ROOT = METAGPT_ROOT / "workspace"
EXAMPLE_PATH = METAGPT_ROOT / "examples"
EXAMPLE_DATA_PATH = EXAMPLE_PATH / "data"
DATA_PATH = METAGPT_ROOT / "data"
+DABENCH_PATH = EXAMPLE_PATH / "di/InfiAgent-DABench/data"
+EXAMPLE_BENCHMARK_PATH = EXAMPLE_PATH / "data/rag_bm"
TEST_DATA_PATH = METAGPT_ROOT / "tests/data"
RESEARCH_PATH = DATA_PATH / "research"
TUTORIAL_PATH = DATA_PATH / "tutorial_docx"
diff --git a/metagpt/document_store/milvus_store.py b/metagpt/document_store/milvus_store.py
new file mode 100644
index 000000000..e4d6d985e
--- /dev/null
+++ b/metagpt/document_store/milvus_store.py
@@ -0,0 +1,99 @@
+from dataclasses import dataclass
+from typing import Any, Dict, List, Optional
+
+from metagpt.document_store.base_store import BaseStore
+
+
+@dataclass
+class MilvusConnection:
+ """
+ Args:
+ uri: milvus url
+ token: milvus token
+ """
+
+ uri: str = None
+ token: str = None
+
+
+class MilvusStore(BaseStore):
+ def __init__(self, connect: MilvusConnection):
+ try:
+ from pymilvus import MilvusClient
+ except ImportError:
+ raise Exception("Please install pymilvus first.")
+ if not connect.uri:
+ raise Exception("please check MilvusConnection, uri must be set.")
+ self.client = MilvusClient(uri=connect.uri, token=connect.token)
+
+ def create_collection(self, collection_name: str, dim: int, enable_dynamic_schema: bool = True):
+ from pymilvus import DataType
+
+ if self.client.has_collection(collection_name=collection_name):
+ self.client.drop_collection(collection_name=collection_name)
+
+ schema = self.client.create_schema(
+ auto_id=False,
+ enable_dynamic_field=False,
+ )
+ schema.add_field(field_name="id", datatype=DataType.VARCHAR, is_primary=True, max_length=36)
+ schema.add_field(field_name="vector", datatype=DataType.FLOAT_VECTOR, dim=dim)
+
+ index_params = self.client.prepare_index_params()
+ index_params.add_index(field_name="vector", index_type="AUTOINDEX", metric_type="COSINE")
+
+ self.client.create_collection(
+ collection_name=collection_name,
+ schema=schema,
+ index_params=index_params,
+ enable_dynamic_schema=enable_dynamic_schema,
+ )
+
+ @staticmethod
+ def build_filter(key, value) -> str:
+ if isinstance(value, str):
+ filter_expression = f'{key} == "{value}"'
+ else:
+ if isinstance(value, list):
+ filter_expression = f"{key} in {value}"
+ else:
+ filter_expression = f"{key} == {value}"
+
+ return filter_expression
+
+ def search(
+ self,
+ collection_name: str,
+ query: List[float],
+ filter: Dict = None,
+ limit: int = 10,
+ output_fields: Optional[List[str]] = None,
+ ) -> List[dict]:
+ filter_expression = " and ".join([self.build_filter(key, value) for key, value in filter.items()])
+ print(filter_expression)
+
+ res = self.client.search(
+ collection_name=collection_name,
+ data=[query],
+ filter=filter_expression,
+ limit=limit,
+ output_fields=output_fields,
+ )[0]
+
+ return res
+
+ def add(self, collection_name: str, _ids: List[str], vector: List[List[float]], metadata: List[Dict[str, Any]]):
+ data = dict()
+
+ for i, id in enumerate(_ids):
+ data["id"] = id
+ data["vector"] = vector[i]
+ data["metadata"] = metadata[i]
+
+ self.client.upsert(collection_name=collection_name, data=data)
+
+ def delete(self, collection_name: str, _ids: List[str]):
+ self.client.delete(collection_name=collection_name, ids=_ids)
+
+ def write(self, *args, **kwargs):
+ pass
diff --git a/metagpt/environment/__init__.py b/metagpt/environment/__init__.py
index 0621fff16..b1d77b3a8 100644
--- a/metagpt/environment/__init__.py
+++ b/metagpt/environment/__init__.py
@@ -3,7 +3,8 @@
# @Desc :
from metagpt.environment.base_env import Environment
-from metagpt.environment.android.android_env import AndroidEnv
+
+# from metagpt.environment.android.android_env import AndroidEnv
from metagpt.environment.werewolf.werewolf_env import WerewolfEnv
from metagpt.environment.stanford_town.stanford_town_env import StanfordTownEnv
from metagpt.environment.software.software_env import SoftwareEnv
diff --git a/metagpt/environment/android/android_env.py b/metagpt/environment/android/android_env.py
index b3227df0f..66672d219 100644
--- a/metagpt/environment/android/android_env.py
+++ b/metagpt/environment/android/android_env.py
@@ -8,6 +8,8 @@ from metagpt.environment.android.android_ext_env import AndroidExtEnv
from metagpt.environment.base_env import Environment
-class AndroidEnv(Environment, AndroidExtEnv):
+class AndroidEnv(AndroidExtEnv, Environment):
+ """in order to use actual `reset`&`observe`, inherited order: AndroidExtEnv, Environment"""
+
rows: int = Field(default=0, description="rows of a grid on the screenshot")
cols: int = Field(default=0, description="cols of a grid on the screenshot")
diff --git a/metagpt/environment/android/android_ext_env.py b/metagpt/environment/android/android_ext_env.py
index 75e2e79ef..63a421fa2 100644
--- a/metagpt/environment/android/android_ext_env.py
+++ b/metagpt/environment/android/android_ext_env.py
@@ -1,16 +1,46 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Desc : The Android external environment to integrate with Android apps
-
import subprocess
+import time
from pathlib import Path
from typing import Any, Optional
+import clip
+from modelscope.pipelines import pipeline
+from modelscope.utils.constant import Tasks
+from PIL import Image
from pydantic import Field
-from metagpt.base.base_env_space import BaseEnvAction, BaseEnvObsParams
+from metagpt.const import DEFAULT_WORKSPACE_ROOT
from metagpt.environment.android.const import ADB_EXEC_FAIL
+from metagpt.environment.android.env_space import (
+ EnvAction,
+ EnvActionType,
+ EnvObsParams,
+ EnvObsType,
+ EnvObsValType,
+)
+from metagpt.environment.android.text_icon_localization import (
+ clip_for_icon,
+ crop_for_clip,
+ det,
+ load_model,
+ ocr,
+)
from metagpt.environment.base_env import ExtEnv, mark_as_readable, mark_as_writeable
+from metagpt.logs import logger
+from metagpt.utils.common import download_model
+
+
+def load_cv_model(device: str = "cpu") -> any:
+ ocr_detection = pipeline(Tasks.ocr_detection, model="damo/cv_resnet18_ocr-detection-line-level_damo")
+ ocr_recognition = pipeline(Tasks.ocr_recognition, model="damo/cv_convnextTiny_ocr-recognition-document_damo")
+ file_url = "https://huggingface.co/ShilongLiu/GroundingDINO/blob/main/groundingdino_swint_ogc.pth"
+ target_folder = Path(f"{DEFAULT_WORKSPACE_ROOT}/weights")
+ file_path = download_model(file_url, target_folder)
+ groundingdino_model = load_model(file_path, device=device).eval()
+ return ocr_detection, ocr_recognition, groundingdino_model
class AndroidExtEnv(ExtEnv):
@@ -19,6 +49,23 @@ class AndroidExtEnv(ExtEnv):
xml_dir: Optional[Path] = Field(default=None)
width: int = Field(default=720, description="device screen width")
height: int = Field(default=1080, description="device screen height")
+ ocr_detection: any = Field(default=None, description="ocr detection model")
+ ocr_recognition: any = Field(default=None, description="ocr recognition model")
+ groundingdino_model: any = Field(default=None, description="clip groundingdino model")
+
+ def __init__(self, **data: Any):
+ super().__init__(**data)
+ device_id = data.get("device_id")
+ self.ocr_detection, self.ocr_recognition, self.groundingdino_model = load_cv_model()
+ if device_id:
+ devices = self.list_devices()
+ if device_id not in devices:
+ raise RuntimeError(f"device-id: {device_id} not found")
+ (width, height) = self.device_shape
+ self.width = data.get("width", width)
+ self.height = data.get("height", height)
+ self.create_device_path(self.screenshot_dir)
+ self.create_device_path(self.xml_dir)
def reset(
self,
@@ -26,20 +73,51 @@ class AndroidExtEnv(ExtEnv):
seed: Optional[int] = None,
options: Optional[dict[str, Any]] = None,
) -> tuple[dict[str, Any], dict[str, Any]]:
+ super().reset(seed=seed, options=options)
+
+ obs = self._get_obs()
+
+ return obs, {}
+
+ def _get_obs(self) -> dict[str, EnvObsValType]:
pass
- def observe(self, obs_params: Optional[BaseEnvObsParams] = None) -> Any:
- pass
+ def observe(self, obs_params: Optional[EnvObsParams] = None) -> Any:
+ obs_type = obs_params.obs_type if obs_params else EnvObsType.NONE
+ if obs_type == EnvObsType.NONE:
+ pass
+ elif obs_type == EnvObsType.GET_SCREENSHOT:
+ obs = self.get_screenshot(ss_name=obs_params.ss_name, local_save_dir=obs_params.local_save_dir)
+ elif obs_type == EnvObsType.GET_XML:
+ obs = self.get_xml(xml_name=obs_params.xml_name, local_save_dir=obs_params.local_save_dir)
+ return obs
- def step(self, action: BaseEnvAction) -> tuple[dict[str, Any], float, bool, bool, dict[str, Any]]:
- pass
+ def step(self, action: EnvAction) -> tuple[dict[str, Any], float, bool, bool, dict[str, Any]]:
+ res = self._execute_env_action(action)
- def __init__(self, **data: Any):
- super().__init__(**data)
- if data.get("device_id"):
- (width, height) = self.device_shape
- self.width = data.get("width", width)
- self.height = data.get("height", height)
+ obs = {}
+
+ ret = (obs, 1.0, False, False, {"res": res})
+ return ret
+
+ def _execute_env_action(self, action: EnvAction):
+ action_type = action.action_type
+ res = None
+ if action_type == EnvActionType.NONE:
+ pass
+ elif action_type == EnvActionType.SYSTEM_BACK:
+ res = self.system_back()
+ elif action_type == EnvActionType.SYSTEM_TAP:
+ res = self.system_tap(x=action.coord[0], y=action.coord[1])
+ elif action_type == EnvActionType.USER_INPUT:
+ res = self.user_input(input_txt=action.input_txt)
+ elif action_type == EnvActionType.USER_LONGPRESS:
+ res = self.user_longpress(x=action.coord[0], y=action.coord[1])
+ elif action_type == EnvActionType.USER_SWIPE:
+ res = self.user_swipe(x=action.coord[0], y=action.coord[1], orient=action.orient, dist=action.dist)
+ elif action_type == EnvActionType.USER_SWIPE_TO:
+ res = self.user_swipe_to(start=action.coord, end=action.tgt_coord)
+ return res
@property
def adb_prefix_si(self):
@@ -57,12 +135,19 @@ class AndroidExtEnv(ExtEnv):
return f"adb -s {self.device_id} "
def execute_adb_with_cmd(self, adb_cmd: str) -> str:
+ adb_cmd = adb_cmd.replace("\\", "/")
res = subprocess.run(adb_cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
exec_res = ADB_EXEC_FAIL
if not res.returncode:
exec_res = res.stdout.strip()
return exec_res
+ def create_device_path(self, folder_path: Path):
+ adb_cmd = f"{self.adb_prefix_shell} mkdir {folder_path} -p"
+ res = self.execute_adb_with_cmd(adb_cmd)
+ if res == ADB_EXEC_FAIL:
+ raise RuntimeError(f"create device path: {folder_path} failed")
+
@property
def device_shape(self) -> tuple[int, int]:
adb_cmd = f"{self.adb_prefix_shell} wm size"
@@ -91,14 +176,26 @@ class AndroidExtEnv(ExtEnv):
ss_remote_path = Path(self.screenshot_dir).joinpath(f"{ss_name}.png")
ss_cmd = f"{self.adb_prefix_shell} screencap -p {ss_remote_path}"
ss_res = self.execute_adb_with_cmd(ss_cmd)
-
+ time.sleep(0.1)
res = ADB_EXEC_FAIL
if ss_res != ADB_EXEC_FAIL:
ss_local_path = Path(local_save_dir).joinpath(f"{ss_name}.png")
pull_cmd = f"{self.adb_prefix} pull {ss_remote_path} {ss_local_path}"
pull_res = self.execute_adb_with_cmd(pull_cmd)
+ time.sleep(0.1)
if pull_res != ADB_EXEC_FAIL:
res = ss_local_path
+ else:
+ ss_cmd = f"{self.adb_prefix_shell} rm /sdcard/{ss_name}.png"
+ ss_res = self.execute_adb_with_cmd(ss_cmd)
+ time.sleep(0.1)
+ ss_cmd = f"{self.adb_prefix_shell} screencap -p /sdcard/{ss_name}.png"
+ ss_res = self.execute_adb_with_cmd(ss_cmd)
+ time.sleep(0.1)
+ ss_cmd = f"{self.adb_prefix} pull /sdcard/{ss_name}.png {self.screenshot_dir}"
+ ss_res = self.execute_adb_with_cmd(ss_cmd)
+ image_path = Path(f"{self.screenshot_dir}/{ss_name}.png")
+ res = image_path
return Path(res)
@mark_as_readable
@@ -166,7 +263,113 @@ class AndroidExtEnv(ExtEnv):
return swipe_res
@mark_as_writeable
- def user_swipe_to(self, start: tuple[int, int], end: tuple[int, int], duration: int = 400):
+ def user_swipe_to(self, start: tuple[int, int], end: tuple[int, int], duration: int = 400) -> str:
adb_cmd = f"{self.adb_prefix_si} swipe {start[0]} {start[1]} {end[0]} {end[1]} {duration}"
swipe_res = self.execute_adb_with_cmd(adb_cmd)
return swipe_res
+
+ @mark_as_writeable
+ def user_exit(self) -> str:
+ adb_cmd = f"{self.adb_prefix_shell} am start -a android.intent.action.MAIN -c android.intent.category.HOME"
+ exit_res = self.execute_adb_with_cmd(adb_cmd)
+ return exit_res
+
+ def _ocr_text(self, text: str) -> list:
+ image = self.get_screenshot("screenshot", self.screenshot_dir)
+ iw, ih = Image.open(image).size
+ x, y = self.device_shape
+ if iw > ih:
+ x, y = y, x
+ iw, ih = ih, iw
+ in_coordinate, out_coordinate = ocr(image, text, self.ocr_detection, self.ocr_recognition, iw, ih)
+ output_list = [in_coordinate, out_coordinate, x, y, iw, ih, image]
+ return output_list
+
+ @mark_as_writeable
+ def user_open_app(self, app_name: str) -> str:
+ ocr_result = self._ocr_text(app_name)
+ in_coordinate, _, x, y, iw, ih = (
+ ocr_result[0],
+ ocr_result[1],
+ ocr_result[2],
+ ocr_result[3],
+ ocr_result[4],
+ ocr_result[5],
+ )
+ if len(in_coordinate) == 0:
+ logger.info(f"No App named {app_name}.")
+ return "no app here"
+ else:
+ tap_coordinate = [
+ (in_coordinate[0][0] + in_coordinate[0][2]) / 2,
+ (in_coordinate[0][1] + in_coordinate[0][3]) / 2,
+ ]
+ tap_coordinate = [round(tap_coordinate[0] / iw, 2), round(tap_coordinate[1] / ih, 2)]
+ return self.system_tap(tap_coordinate[0] * x, (tap_coordinate[1] - round(50 / y, 2)) * y)
+
+ @mark_as_writeable
+ def user_click_text(self, text: str) -> str:
+ ocr_result = self._ocr_text(text)
+ in_coordinate, out_coordinate, x, y, iw, ih, _ = (
+ ocr_result[0],
+ ocr_result[1],
+ ocr_result[2],
+ ocr_result[3],
+ ocr_result[4],
+ ocr_result[5],
+ ocr_result[6],
+ )
+ if len(out_coordinate) == 0:
+ logger.info(
+ f'Failed to execute action click text ({text}). The text "{text}" is not detected in the screenshot.'
+ )
+ elif len(out_coordinate) == 1:
+ tap_coordinate = [
+ (in_coordinate[0][0] + in_coordinate[0][2]) / 2,
+ (in_coordinate[0][1] + in_coordinate[0][3]) / 2,
+ ]
+ tap_coordinate = [round(tap_coordinate[0] / iw, 2), round(tap_coordinate[1] / ih, 2)]
+ return self.system_tap(tap_coordinate[0] * x, tap_coordinate[1] * y)
+ else:
+ logger.info(
+ f'Failed to execute action click text ({text}). There are too many text "{text}" in the screenshot.'
+ )
+
+ @mark_as_writeable
+ def user_stop(self):
+ logger.info("Successful execution of tasks")
+
+ @mark_as_writeable
+ def user_click_icon(self, icon_shape_color: str) -> str:
+ screenshot_path = self.get_screenshot("screenshot", self.screenshot_dir)
+ image = screenshot_path
+ iw, ih = Image.open(image).size
+ x, y = self.device_shape
+ if iw > ih:
+ x, y = y, x
+ iw, ih = ih, iw
+ in_coordinate, out_coordinate = det(image, "icon", self.groundingdino_model) # 检测icon
+ if len(out_coordinate) == 1: # only one icon
+ tap_coordinate = [
+ (in_coordinate[0][0] + in_coordinate[0][2]) / 2,
+ (in_coordinate[0][1] + in_coordinate[0][3]) / 2,
+ ]
+ tap_coordinate = [round(tap_coordinate[0] / iw, 2), round(tap_coordinate[1] / ih, 2)]
+ return self.system_tap(tap_coordinate[0] * x, tap_coordinate[1] * y)
+
+ else:
+ temp_file = Path(f"{DEFAULT_WORKSPACE_ROOT}/temp")
+ temp_file.mkdir(parents=True, exist_ok=True)
+ hash_table, clip_filter = [], []
+ for i, (td, box) in enumerate(zip(in_coordinate, out_coordinate)):
+ if crop_for_clip(image, td, i, temp_file):
+ hash_table.append(td)
+ crop_image = f"{i}.png"
+ clip_filter.append(temp_file.joinpath(crop_image))
+ clip_model, clip_preprocess = clip.load("ViT-B/32") # FIXME: device=device
+ clip_filter = clip_for_icon(clip_model, clip_preprocess, clip_filter, icon_shape_color)
+ final_box = hash_table[clip_filter]
+ tap_coordinate = [(final_box[0] + final_box[2]) / 2, (final_box[1] + final_box[3]) / 2]
+ tap_coordinate = [round(tap_coordinate[0] / iw, 2), round(tap_coordinate[1] / ih, 2)]
+ print(tap_coordinate[0] * x, tap_coordinate[1] * y)
+ return self.system_tap(tap_coordinate[0] * x, tap_coordinate[1] * y)
diff --git a/metagpt/environment/android/env_space.py b/metagpt/environment/android/env_space.py
new file mode 100644
index 000000000..9580e3a7d
--- /dev/null
+++ b/metagpt/environment/android/env_space.py
@@ -0,0 +1,92 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# @Desc :
+
+from pathlib import Path
+from typing import Union
+
+import numpy as np
+import numpy.typing as npt
+from gymnasium import spaces
+from pydantic import ConfigDict, Field, field_validator
+
+from metagpt.environment.base_env_space import (
+ BaseEnvAction,
+ BaseEnvActionType,
+ BaseEnvObsParams,
+ BaseEnvObsType,
+)
+
+
+class EnvActionType(BaseEnvActionType):
+ NONE = 0 # no action to run, just get observation
+
+ SYSTEM_BACK = 1
+ SYSTEM_TAP = 2
+ USER_INPUT = 3
+ USER_LONGPRESS = 4
+ USER_SWIPE = 5
+ USER_SWIPE_TO = 6
+
+
+class EnvAction(BaseEnvAction):
+ model_config = ConfigDict(arbitrary_types_allowed=True)
+
+ action_type: int = Field(default=EnvActionType.NONE, description="action type")
+ coord: npt.NDArray[np.int64] = Field(
+ default_factory=lambda: np.zeros(2, dtype=np.int64), description="operation coordinate"
+ )
+ tgt_coord: npt.NDArray[np.int64] = Field(
+ default_factory=lambda: np.zeros(2, dtype=np.int64), description="target operation coordinate"
+ )
+ input_txt: str = Field(default="", description="user input text")
+ orient: str = Field(default="up", description="swipe orient")
+ dist: str = Field(default="medium", description="swipe dist")
+
+ @field_validator("coord", "tgt_coord", mode="before")
+ @classmethod
+ def check_coord(cls, coord) -> npt.NDArray[np.int64]:
+ if not isinstance(coord, np.ndarray):
+ return np.array(coord)
+
+
+class EnvObsType(BaseEnvObsType):
+ NONE = 0 # get whole observation from env
+
+ GET_SCREENSHOT = 1
+ GET_XML = 2
+
+
+class EnvObsParams(BaseEnvObsParams):
+ model_config = ConfigDict(arbitrary_types_allowed=True)
+
+ obs_type: int = Field(default=EnvObsType.NONE, description="observation type")
+ ss_name: str = Field(default="", description="screenshot file name")
+ xml_name: str = Field(default="", description="xml file name")
+ local_save_dir: Union[str, Path] = Field(default="", description="local dir to save file")
+
+
+EnvObsValType = str
+
+
+def get_observation_space() -> spaces.Dict:
+ space = spaces.Dict({"screenshot": spaces.Text(256), "xml": spaces.Text(256)})
+ return space
+
+
+def get_action_space(device_shape: tuple[int, int]) -> spaces.Dict:
+ space = spaces.Dict(
+ {
+ "action_type": spaces.Discrete(len(EnvActionType)),
+ "coord": spaces.Box(
+ np.array([0, 0], dtype=np.int64), np.array([device_shape[0], device_shape[1]], dtype=np.int64)
+ ),
+ "tgt_coord": spaces.Box(
+ np.array([0, 0], dtype=np.int64), np.array([device_shape[0], device_shape[1]], dtype=np.int64)
+ ),
+ "input_txt": spaces.Text(256),
+ "orient": spaces.Text(16),
+ "dist": spaces.Text(16),
+ }
+ )
+ return space
diff --git a/metagpt/environment/android/grounding_dino_config.py b/metagpt/environment/android/grounding_dino_config.py
new file mode 100644
index 000000000..9158d5f62
--- /dev/null
+++ b/metagpt/environment/android/grounding_dino_config.py
@@ -0,0 +1,43 @@
+batch_size = 1
+modelname = "groundingdino"
+backbone = "swin_T_224_1k"
+position_embedding = "sine"
+pe_temperatureH = 20
+pe_temperatureW = 20
+return_interm_indices = [1, 2, 3]
+backbone_freeze_keywords = None
+enc_layers = 6
+dec_layers = 6
+pre_norm = False
+dim_feedforward = 2048
+hidden_dim = 256
+dropout = 0.0
+nheads = 8
+num_queries = 900
+query_dim = 4
+num_patterns = 0
+num_feature_levels = 4
+enc_n_points = 4
+dec_n_points = 4
+two_stage_type = "standard"
+two_stage_bbox_embed_share = False
+two_stage_class_embed_share = False
+transformer_activation = "relu"
+dec_pred_bbox_embed_share = True
+dn_box_noise_scale = 1.0
+dn_label_noise_ratio = 0.5
+dn_label_coef = 1.0
+dn_bbox_coef = 1.0
+embed_init_tgt = True
+dn_labelbook_size = 2000
+max_text_len = 256
+text_encoder_type = "bert-base-uncased"
+use_text_enhancer = True
+use_fusion_layer = True
+use_checkpoint = True
+use_transformer_ckpt = True
+use_text_cross_attention = True
+text_dropout = 0.0
+fusion_dropout = 0.0
+fusion_droppath = 0.1
+sub_sentence_present = True
diff --git a/metagpt/environment/android/text_icon_localization.py b/metagpt/environment/android/text_icon_localization.py
new file mode 100644
index 000000000..e8886b540
--- /dev/null
+++ b/metagpt/environment/android/text_icon_localization.py
@@ -0,0 +1,368 @@
+# The code in this file was modified by MobileAgent
+# https://github.com/X-PLUG/MobileAgent.git
+
+import math
+from pathlib import Path
+
+import clip
+import cv2
+import groundingdino.datasets.transforms as T
+import numpy as np
+import torch
+from groundingdino.models import build_model
+from groundingdino.util.slconfig import SLConfig
+from groundingdino.util.utils import clean_state_dict, get_phrases_from_posmap
+from PIL import Image
+
+################################## text_localization using ocr #######################
+
+
+def crop_image(img: any, position: any) -> any:
+ def distance(x1, y1, x2, y2):
+ return math.sqrt(pow(x1 - x2, 2) + pow(y1 - y2, 2))
+
+ position = position.tolist()
+ for i in range(4):
+ for j in range(i + 1, 4):
+ if position[i][0] > position[j][0]:
+ tmp = position[j]
+ position[j] = position[i]
+ position[i] = tmp
+ if position[0][1] > position[1][1]:
+ tmp = position[0]
+ position[0] = position[1]
+ position[1] = tmp
+
+ if position[2][1] > position[3][1]:
+ tmp = position[2]
+ position[2] = position[3]
+ position[3] = tmp
+
+ x1, y1 = position[0][0], position[0][1]
+ x2, y2 = position[2][0], position[2][1]
+ x3, y3 = position[3][0], position[3][1]
+ x4, y4 = position[1][0], position[1][1]
+
+ corners = np.zeros((4, 2), np.float32)
+ corners[0] = [x1, y1]
+ corners[1] = [x2, y2]
+ corners[2] = [x4, y4]
+ corners[3] = [x3, y3]
+
+ img_width = distance((x1 + x4) / 2, (y1 + y4) / 2, (x2 + x3) / 2, (y2 + y3) / 2)
+ img_height = distance((x1 + x2) / 2, (y1 + y2) / 2, (x4 + x3) / 2, (y4 + y3) / 2)
+
+ corners_trans = np.zeros((4, 2), np.float32)
+ corners_trans[0] = [0, 0]
+ corners_trans[1] = [img_width - 1, 0]
+ corners_trans[2] = [0, img_height - 1]
+ corners_trans[3] = [img_width - 1, img_height - 1]
+
+ transform = cv2.getPerspectiveTransform(corners, corners_trans)
+ dst = cv2.warpPerspective(img, transform, (int(img_width), int(img_height)))
+ return dst
+
+
+def calculate_size(box: any) -> any:
+ return (box[2] - box[0]) * (box[3] - box[1])
+
+
+def order_point(cooperation: any) -> any:
+ arr = np.array(cooperation).reshape([4, 2])
+ sum_ = np.sum(arr, 0)
+ centroid = sum_ / arr.shape[0]
+ theta = np.arctan2(arr[:, 1] - centroid[1], arr[:, 0] - centroid[0])
+ sort_points = arr[np.argsort(theta)]
+ sort_points = sort_points.reshape([4, -1])
+ if sort_points[0][0] > centroid[0]:
+ sort_points = np.concatenate([sort_points[3:], sort_points[:3]])
+ sort_points = sort_points.reshape([4, 2]).astype("float32")
+ return sort_points
+
+
+def longest_common_substring_length(str1: str, str2: str) -> int:
+ m = len(str1)
+ n = len(str2)
+ dp = [[0] * (n + 1) for _ in range(m + 1)]
+ for i in range(1, m + 1):
+ for j in range(1, n + 1):
+ if str1[i - 1] == str2[j - 1]:
+ dp[i][j] = dp[i - 1][j - 1] + 1
+ else:
+ dp[i][j] = max(dp[i - 1][j], dp[i][j - 1])
+
+ return dp[m][n]
+
+
+def ocr(image_path: Path, prompt: str, ocr_detection: any, ocr_recognition: any, x: int, y: int) -> any:
+ text_data = []
+ coordinate = []
+ image = Image.open(image_path)
+ iw, ih = image.size
+
+ image_full = cv2.imread(str(image_path))
+ det_result = ocr_detection(image_full)
+ det_result = det_result["polygons"]
+ for i in range(det_result.shape[0]):
+ pts = order_point(det_result[i])
+ image_crop = crop_image(image_full, pts)
+ result = ocr_recognition(image_crop)["text"][0]
+
+ if result == prompt:
+ box = [int(e) for e in list(pts.reshape(-1))]
+ box = [box[0], box[1], box[4], box[5]]
+
+ if calculate_size(box) > 0.05 * iw * ih:
+ continue
+
+ text_data.append(
+ [
+ int(max(0, box[0] - 10) * x / iw),
+ int(max(0, box[1] - 10) * y / ih),
+ int(min(box[2] + 10, iw) * x / iw),
+ int(min(box[3] + 10, ih) * y / ih),
+ ]
+ )
+ coordinate.append(
+ [
+ int(max(0, box[0] - 300) * x / iw),
+ int(max(0, box[1] - 400) * y / ih),
+ int(min(box[2] + 300, iw) * x / iw),
+ int(min(box[3] + 400, ih) * y / ih),
+ ]
+ )
+
+ max_length = 0
+ if len(text_data) == 0:
+ for i in range(det_result.shape[0]):
+ pts = order_point(det_result[i])
+ image_crop = crop_image(image_full, pts)
+ result = ocr_recognition(image_crop)["text"][0]
+
+ if len(result) < 0.3 * len(prompt):
+ continue
+
+ if result in prompt:
+ now_length = len(result)
+ else:
+ now_length = longest_common_substring_length(result, prompt)
+
+ if now_length > max_length:
+ max_length = now_length
+ box = [int(e) for e in list(pts.reshape(-1))]
+ box = [box[0], box[1], box[4], box[5]]
+
+ text_data = [
+ [
+ int(max(0, box[0] - 10) * x / iw),
+ int(max(0, box[1] - 10) * y / ih),
+ int(min(box[2] + 10, iw) * x / iw),
+ int(min(box[3] + 10, ih) * y / ih),
+ ]
+ ]
+ coordinate = [
+ [
+ int(max(0, box[0] - 300) * x / iw),
+ int(max(0, box[1] - 400) * y / ih),
+ int(min(box[2] + 300, iw) * x / iw),
+ int(min(box[3] + 400, ih) * y / ih),
+ ]
+ ]
+
+ if len(prompt) <= 10:
+ if max_length >= 0.8 * len(prompt):
+ return text_data, coordinate
+ else:
+ return [], []
+ elif (len(prompt) > 10) and (len(prompt) <= 20):
+ if max_length >= 0.5 * len(prompt):
+ return text_data, coordinate
+ else:
+ return [], []
+ else:
+ if max_length >= 0.4 * len(prompt):
+ return text_data, coordinate
+ else:
+ return [], []
+
+ else:
+ return text_data, coordinate
+
+
+################################## icon_localization using clip #######################
+
+
+def calculate_iou(box1: list, box2: list) -> float:
+ x_a = max(box1[0], box2[0])
+ y_a = max(box1[1], box2[1])
+ x_b = min(box1[2], box2[2])
+ y_b = min(box1[3], box2[3])
+
+ inter_area = max(0, x_b - x_a) * max(0, y_b - y_a)
+ box1_area = (box1[2] - box1[0]) * (box1[3] - box1[1])
+ box2_area = (box2[2] - box2[0]) * (box2[3] - box2[1])
+ union_area = box1_area + box2_area - inter_area
+ iou = inter_area / union_area
+
+ return iou
+
+
+def in_box(box: list, target: list) -> bool:
+ if (box[0] > target[0]) and (box[1] > target[1]) and (box[2] < target[2]) and (box[3] < target[3]):
+ return True
+ else:
+ return False
+
+
+def crop_for_clip(image: any, box: any, i: int, temp_file: Path) -> bool:
+ image = Image.open(image)
+ w, h = image.size
+ bound = [0, 0, w, h]
+ if in_box(box, bound):
+ cropped_image = image.crop(box)
+ cropped_image.save(temp_file.joinpath(f"{i}.png"))
+ return True
+ else:
+ return False
+
+
+def clip_for_icon(clip_model: any, clip_preprocess: any, images: any, prompt: str) -> any:
+ image_features = []
+ for image_file in images:
+ image = clip_preprocess(Image.open(image_file)).unsqueeze(0).to(next(clip_model.parameters()).device)
+ image_feature = clip_model.encode_image(image)
+ image_features.append(image_feature)
+ image_features = torch.cat(image_features)
+
+ text = clip.tokenize([prompt]).to(next(clip_model.parameters()).device)
+ text_features = clip_model.encode_text(text)
+
+ image_features /= image_features.norm(dim=-1, keepdim=True)
+ text_features /= text_features.norm(dim=-1, keepdim=True)
+ similarity = (100.0 * image_features @ text_features.T).softmax(dim=0).squeeze(0)
+ _, max_pos = torch.max(similarity, dim=0)
+ pos = max_pos.item()
+
+ return pos
+
+
+def transform_image(image_pil: any) -> any:
+ transform = T.Compose(
+ [
+ T.RandomResize([800], max_size=1333),
+ T.ToTensor(),
+ T.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]),
+ ]
+ )
+ image, _ = transform(image_pil, None) # 3, h, w
+ return image
+
+
+def load_model(model_checkpoint_path: Path, device: str) -> any:
+ model_config_path = "grounding_dino_config.py"
+ args = SLConfig.fromfile(model_config_path)
+ args.device = device
+ model = build_model(args)
+ checkpoint = torch.load(model_checkpoint_path, map_location="cpu")
+ load_res = model.load_state_dict(clean_state_dict(checkpoint["model"]), strict=False)
+ print(load_res)
+ _ = model.eval()
+ return model
+
+
+def get_grounding_output(
+ model: any, image: any, caption: str, box_threshold: any, text_threshold: any, with_logits: bool = True
+) -> any:
+ caption = caption.lower()
+ caption = caption.strip()
+ if not caption.endswith("."):
+ caption = caption + "."
+
+ with torch.no_grad():
+ outputs = model(image[None], captions=[caption])
+ logits = outputs["pred_logits"].cpu().sigmoid()[0] # (nq, 256)
+ boxes = outputs["pred_boxes"].cpu()[0] # (nq, 4)
+ logits.shape[0]
+
+ logits_filt = logits.clone()
+ boxes_filt = boxes.clone()
+ filt_mask = logits_filt.max(dim=1)[0] > box_threshold
+ logits_filt = logits_filt[filt_mask] # num_filt, 256
+ boxes_filt = boxes_filt[filt_mask] # num_filt, 4
+ logits_filt.shape[0]
+
+ tokenlizer = model.tokenizer
+ tokenized = tokenlizer(caption)
+
+ pred_phrases = []
+ scores = []
+ for logit, box in zip(logits_filt, boxes_filt):
+ pred_phrase = get_phrases_from_posmap(logit > text_threshold, tokenized, tokenlizer)
+ if with_logits:
+ pred_phrases.append(pred_phrase + f"({str(logit.max().item())[:4]})")
+ else:
+ pred_phrases.append(pred_phrase)
+ scores.append(logit.max().item())
+
+ return boxes_filt, torch.Tensor(scores), pred_phrases
+
+
+def remove_boxes(boxes_filt: any, size: any, iou_threshold: float = 0.5) -> any:
+ boxes_to_remove = set()
+
+ for i in range(len(boxes_filt)):
+ if calculate_size(boxes_filt[i]) > 0.05 * size[0] * size[1]:
+ boxes_to_remove.add(i)
+ for j in range(len(boxes_filt)):
+ if calculate_size(boxes_filt[j]) > 0.05 * size[0] * size[1]:
+ boxes_to_remove.add(j)
+ if i == j:
+ continue
+ if i in boxes_to_remove or j in boxes_to_remove:
+ continue
+ iou = calculate_iou(boxes_filt[i], boxes_filt[j])
+ if iou >= iou_threshold:
+ boxes_to_remove.add(j)
+
+ boxes_filt = [box for idx, box in enumerate(boxes_filt) if idx not in boxes_to_remove]
+
+ return boxes_filt
+
+
+def det(
+ input_image: any,
+ text_prompt: str,
+ groundingdino_model: any,
+ box_threshold: float = 0.05,
+ text_threshold: float = 0.5,
+) -> any:
+ image = Image.open(input_image)
+ size = image.size
+
+ image_pil = image.convert("RGB")
+ image = np.array(image_pil)
+
+ transformed_image = transform_image(image_pil)
+ boxes_filt, scores, pred_phrases = get_grounding_output(
+ groundingdino_model, transformed_image, text_prompt, box_threshold, text_threshold
+ )
+
+ H, W = size[1], size[0]
+ for i in range(boxes_filt.size(0)):
+ boxes_filt[i] = boxes_filt[i] * torch.Tensor([W, H, W, H])
+ boxes_filt[i][:2] -= boxes_filt[i][2:] / 2
+ boxes_filt[i][2:] += boxes_filt[i][:2]
+
+ boxes_filt = boxes_filt.cpu().int().tolist()
+ filtered_boxes = remove_boxes(boxes_filt, size) # [:9]
+ coordinate = []
+ image_data = []
+ for box in filtered_boxes:
+ image_data.append(
+ [max(0, box[0] - 10), max(0, box[1] - 10), min(box[2] + 10, size[0]), min(box[3] + 10, size[1])]
+ )
+ coordinate.append(
+ [max(0, box[0] - 25), max(0, box[1] - 25), min(box[2] + 25, size[0]), min(box[3] + 25, size[1])]
+ )
+
+ return image_data, coordinate
diff --git a/metagpt/environment/api/env_api.py b/metagpt/environment/api/env_api.py
index 1e6df544d..924f6b104 100644
--- a/metagpt/environment/api/env_api.py
+++ b/metagpt/environment/api/env_api.py
@@ -18,11 +18,11 @@ class EnvAPIAbstract(BaseModel):
class EnvAPIRegistry(BaseModel):
"""the registry to store environment w&r api/interface"""
- registry: dict[str, dict[str, Union[dict, Any, str]]] = Field(default=dict(), exclude=True)
+ registry: dict[str, Callable] = Field(default=dict(), exclude=True)
def get(self, api_name: str):
if api_name not in self.registry:
- raise ValueError
+ raise KeyError(f"api_name: {api_name} not found")
return self.registry.get(api_name)
def __getitem__(self, api_name: str) -> Callable:
diff --git a/metagpt/environment/minecraft/minecraft_env.py b/metagpt/environment/minecraft/minecraft_env.py
index 2bf39095c..9c42949a6 100644
--- a/metagpt/environment/minecraft/minecraft_env.py
+++ b/metagpt/environment/minecraft/minecraft_env.py
@@ -19,7 +19,7 @@ from metagpt.logs import logger
from metagpt.utils.common import load_mc_skills_code, read_json_file, write_json_file
-class MinecraftEnv(Environment, MinecraftExtEnv):
+class MinecraftEnv(MinecraftExtEnv, Environment):
"""MinecraftEnv, including shared memory of cache and information between roles"""
model_config = ConfigDict(arbitrary_types_allowed=True)
diff --git a/metagpt/environment/werewolf/const.py b/metagpt/environment/werewolf/const.py
new file mode 100644
index 000000000..7f810389d
--- /dev/null
+++ b/metagpt/environment/werewolf/const.py
@@ -0,0 +1,121 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# @Desc :
+
+from enum import Enum
+
+from metagpt.const import MESSAGE_ROUTE_TO_ALL
+
+
+class RoleType(Enum):
+ VILLAGER = "Villager"
+ WEREWOLF = "Werewolf"
+ GUARD = "Guard"
+ SEER = "Seer"
+ WITCH = "Witch"
+ MODERATOR = "Moderator"
+
+
+class RoleState(Enum):
+ ALIVE = "alive" # the role is alive
+ DEAD = "dead" # killed or poisoned
+ KILLED = "killed" # killed by werewolf or voting
+ POISONED = "poisoned" # killed by poison
+ SAVED = "saved" # saved by antidote
+ PROTECTED = "projected" # projected by guard
+
+
+class RoleActionRes(Enum):
+ SAVE = "save"
+ PASS = "pass" # ignore current action output
+
+
+empty_set = set()
+
+# the ordered rules by the moderator to announce to everyone each step
+STEP_INSTRUCTIONS = {
+ 0: {
+ "content": "It’s dark, everyone close your eyes. I will talk with you/your team secretly at night.",
+ "send_to": {RoleType.MODERATOR.value}, # for moderator to continue speaking
+ "restricted_to": empty_set,
+ },
+ 1: {
+ "content": "Guard, please open your eyes!",
+ "send_to": {RoleType.MODERATOR.value}, # for moderator to continue speaking
+ "restricted_to": empty_set,
+ },
+ 2: {
+ "content": """Guard, now tell me who you protect tonight?
+You only choose one from the following living options please: {living_players}.
+Or you can pass. For example: Protect ...""",
+ "send_to": {RoleType.GUARD.value},
+ "restricted_to": {RoleType.MODERATOR.value, RoleType.GUARD.value},
+ },
+ 3: {"content": "Guard, close your eyes", "send_to": {RoleType.MODERATOR.value}, "restricted_to": empty_set},
+ 4: {
+ "content": "Werewolves, please open your eyes!",
+ "send_to": {RoleType.MODERATOR.value},
+ "restricted_to": empty_set,
+ },
+ 5: {
+ "content": """Werewolves, I secretly tell you that {werewolf_players} are
+all of the {werewolf_num} werewolves! Keep in mind you are teammates. The rest players are not werewolves.
+choose one from the following living options please:
+{living_players}. For example: Kill ...""",
+ "send_to": {RoleType.WEREWOLF.value},
+ "restricted_to": {RoleType.MODERATOR.value, RoleType.WEREWOLF.value},
+ },
+ 6: {"content": "Werewolves, close your eyes", "send_to": {RoleType.MODERATOR.value}, "restricted_to": empty_set},
+ 7: {"content": "Witch, please open your eyes!", "send_to": {RoleType.MODERATOR.value}, "restricted_to": empty_set},
+ 8: {
+ "content": """Witch, tonight {player_hunted} has been killed by the werewolves.
+You have a bottle of antidote, would you like to save him/her? If so, say "Save", else, say "Pass".""",
+ "send_to": {RoleType.WITCH.value},
+ "restricted_to": {RoleType.MODERATOR.value, RoleType.WITCH.value},
+ }, # 要先判断女巫是否有解药,再去询问女巫是否使用解药救人
+ 9: {
+ "content": """Witch, you also have a bottle of poison, would you like to use it to kill one of the living players?
+Choose one from the following living options: {living_players}.
+If so, say ONLY "Poison PlayerX", replace PlayerX with the actual player name, else, say "Pass".""",
+ "send_to": {RoleType.WITCH.value},
+ "restricted_to": {RoleType.MODERATOR.value, RoleType.WITCH.value},
+ }, #
+ 10: {"content": "Witch, close your eyes", "send_to": {RoleType.MODERATOR.value}, "restricted_to": empty_set},
+ 11: {"content": "Seer, please open your eyes!", "send_to": {RoleType.MODERATOR.value}, "restricted_to": empty_set},
+ 12: {
+ "content": """Seer, you can check one player's identity. Who are you going to verify its identity tonight?
+Choose only one from the following living options:{living_players}.""",
+ "send_to": {RoleType.SEER.value},
+ "restricted_to": {RoleType.MODERATOR.value, RoleType.SEER.value},
+ },
+ 13: {"content": "Seer, close your eyes", "send_to": {RoleType.MODERATOR.value}, "restricted_to": empty_set},
+ # The 1-st daytime
+ 14: {
+ "content": """It's daytime. Everyone woke up except those who had been killed.""",
+ "send_to": {RoleType.MODERATOR.value},
+ "restricted_to": empty_set,
+ },
+ 15: {
+ "content": "{player_current_dead} was killed last night!",
+ "send_to": {RoleType.MODERATOR.value},
+ "restricted_to": empty_set,
+ },
+ 16: {
+ "content": """Living players: {living_players}, now freely talk about the current situation based on your observation and
+reflection with a few sentences. Decide whether to reveal your identity based on your reflection.""",
+ "send_to": {MESSAGE_ROUTE_TO_ALL}, # send to all to speak in daytime
+ "restricted_to": empty_set,
+ },
+ 17: {
+ "content": """Now vote and tell me who you think is the werewolf. Don’t mention your role.
+You only choose one from the following living options please:
+{living_players}. Say ONLY: I vote to eliminate ...""",
+ "send_to": {MESSAGE_ROUTE_TO_ALL},
+ "restricted_to": empty_set,
+ },
+ 18: {
+ "content": """{player_current_dead} was eliminated.""",
+ "send_to": {RoleType.MODERATOR.value},
+ "restricted_to": empty_set,
+ },
+}
diff --git a/metagpt/environment/werewolf/env_space.py b/metagpt/environment/werewolf/env_space.py
new file mode 100644
index 000000000..e6243b10f
--- /dev/null
+++ b/metagpt/environment/werewolf/env_space.py
@@ -0,0 +1,62 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# @Desc : werewolf observation/action space and its action definition
+
+from gymnasium import spaces
+from pydantic import ConfigDict, Field
+
+from metagpt.environment.base_env_space import BaseEnvAction, BaseEnvActionType
+from metagpt.environment.werewolf.const import STEP_INSTRUCTIONS
+
+
+class EnvActionType(BaseEnvActionType):
+ NONE = 0 # no action to run, just get observation
+ WOLF_KILL = 1 # wolf kill someone
+ VOTE_KILL = 2 # vote kill someone
+ WITCH_POISON = 3 # witch poison someone
+ WITCH_SAVE = 4 # witch save someone
+ GUARD_PROTECT = 5 # guard protect someone
+ PROGRESS_STEP = 6 # step increment
+
+
+class EnvAction(BaseEnvAction):
+ model_config = ConfigDict(arbitrary_types_allowed=True)
+
+ action_type: int = Field(default=EnvActionType.NONE, description="action type")
+ player_name: str = Field(default="", description="the name of the player to do the action")
+ target_player_name: str = Field(default="", description="the name of the player who take the action")
+
+
+def get_observation_space() -> spaces.Dict:
+ space = spaces.Dict(
+ {
+ "game_setup": spaces.Text(256),
+ "step_idx": spaces.Discrete(len(STEP_INSTRUCTIONS)),
+ "living_players": spaces.Tuple(
+ (spaces.Text(16), spaces.Text(16))
+ ), # TODO should be tuple of variable length
+ "werewolf_players": spaces.Tuple(
+ (spaces.Text(16), spaces.Text(16))
+ ), # TODO should be tuple of variable length
+ "player_hunted": spaces.Text(16),
+ "player_current_dead": spaces.Tuple(
+ (spaces.Text(16), spaces.Text(16))
+ ), # TODO should be tuple of variable length
+ "witch_poison_left": spaces.Discrete(2),
+ "witch_antidote_left": spaces.Discrete(2),
+ "winner": spaces.Text(16),
+ "win_reason": spaces.Text(64),
+ }
+ )
+ return space
+
+
+def get_action_space() -> spaces.Dict:
+ space = spaces.Dict(
+ {
+ "action_type": spaces.Discrete(len(EnvActionType)),
+ "player_name": spaces.Text(16), # the player to do the action
+ "target_player_name": spaces.Text(16), # the target player who take the action
+ }
+ )
+ return space
diff --git a/metagpt/environment/werewolf/werewolf_env.py b/metagpt/environment/werewolf/werewolf_env.py
index e22d9c46f..999ff63a1 100644
--- a/metagpt/environment/werewolf/werewolf_env.py
+++ b/metagpt/environment/werewolf/werewolf_env.py
@@ -2,30 +2,40 @@
# -*- coding: utf-8 -*-
# @Desc : MG Werewolf Env
+from typing import Iterable
+
from pydantic import Field
from metagpt.environment.base_env import Environment
from metagpt.environment.werewolf.werewolf_ext_env import WerewolfExtEnv
-from metagpt.logs import logger
from metagpt.schema import Message
-class WerewolfEnv(Environment, WerewolfExtEnv):
- timestamp: int = Field(default=0)
+class WerewolfEnv(WerewolfExtEnv, Environment):
+ round_cnt: int = Field(default=0)
+
+ def add_roles(self, roles: Iterable["Role"]):
+ """增加一批在当前环境的角色
+ Add a batch of characters in the current environment
+ """
+ for role in roles:
+ self.roles[role.name] = role # use name as key here, due to multi-player can have same profile
+
+ for role in roles: # setup system message with roles
+ role.context = self.context
+ role.set_env(self)
def publish_message(self, message: Message, add_timestamp: bool = True):
"""Post information to the current environment"""
- logger.debug(f"publish_message: {message.dump()}")
if add_timestamp:
# Because the content of the message may be repeated, for example, killing the same person in two nights
- # Therefore, a unique timestamp prefix needs to be added so that the same message will not be automatically deduplicated when added to the memory.
- message.content = f"{self.timestamp} | " + message.content
- self.memory.add(message)
- self.history += f"\n{message}"
+ # Therefore, a unique round_cnt prefix needs to be added so that the same message will not be automatically deduplicated when added to the memory.
+ message.content = f"{self.round_cnt} | " + message.content
+ super().publish_message(message)
async def run(self, k=1):
"""Process all Role runs by order"""
for _ in range(k):
for role in self.roles.values():
await role.run()
- self.timestamp += 1
+ self.round_cnt += 1
diff --git a/metagpt/environment/werewolf/werewolf_ext_env.py b/metagpt/environment/werewolf/werewolf_ext_env.py
index d9644eb9b..c5ecb8b34 100644
--- a/metagpt/environment/werewolf/werewolf_ext_env.py
+++ b/metagpt/environment/werewolf/werewolf_ext_env.py
@@ -4,110 +4,27 @@
import random
from collections import Counter
-from enum import Enum
from typing import Any, Callable, Optional
from pydantic import ConfigDict, Field
-from metagpt.base.base_env_space import BaseEnvAction, BaseEnvObsParams
+from metagpt.base.base_env_space import BaseEnvObsParams
from metagpt.environment.base_env import ExtEnv, mark_as_readable, mark_as_writeable
+from metagpt.environment.werewolf.const import STEP_INSTRUCTIONS, RoleState, RoleType
+from metagpt.environment.werewolf.env_space import EnvAction, EnvActionType
from metagpt.logs import logger
-class RoleState(Enum):
- ALIVE = "alive" # the role is alive
- KILLED = "killed" # the role is killed by werewolf or voting
- POISONED = "poisoned" # the role is killed by posion
- SAVED = "saved" # the role is saved by antidote
-
-
-# the ordered rules by the moderator to announce to everyone each step
-STEP_INSTRUCTIONS = {
- 0: {
- "content": "It’s dark, everyone close your eyes. I will talk with you/your team secretly at night.",
- "send_to": "Moderator", # for moderator to continuen speaking
- "restricted_to": "",
- },
- 1: {
- "content": "Guard, please open your eyes!",
- "send_to": "Moderator", # for moderator to continuen speaking
- "restricted_to": "",
- },
- 2: {
- "content": """Guard, now tell me who you protect tonight?
- You only choose one from the following living options please: {living_players}.
- Or you can pass. For example: Protect ...""",
- "send_to": "Guard",
- "restricted_to": "Moderator,Guard",
- },
- 3: {"content": "Guard, close your eyes", "send_to": "Moderator", "restricted_to": ""},
- 4: {"content": "Werewolves, please open your eyes!", "send_to": "Moderator", "restricted_to": ""},
- 5: {
- "content": """Werewolves, I secretly tell you that {werewolf_players} are
- all of the 2 werewolves! Keep in mind you are teammates. The rest players are not werewolves.
- choose one from the following living options please:
- {living_players}. For example: Kill ...""",
- "send_to": "Werewolf",
- "restricted_to": "Moderator,Werewolf",
- },
- 6: {"content": "Werewolves, close your eyes", "send_to": "Moderator", "restricted_to": ""},
- 7: {"content": "Witch, please open your eyes!", "send_to": "Moderator", "restricted_to": ""},
- 8: {
- "content": """Witch, tonight {player_hunted} has been killed by the werewolves.
- You have a bottle of antidote, would you like to save him/her? If so, say "Save", else, say "Pass".""",
- "send_to": "Witch",
- "restricted_to": "Moderator,Witch",
- }, # 要先判断女巫是否有解药,再去询问女巫是否使用解药救人
- 9: {
- "content": """Witch, you also have a bottle of poison, would you like to use it to kill one of the living players?
- Choose one from the following living options: {living_players}.
- If so, say ONLY "Poison PlayerX", replace PlayerX with the actual player name, else, say "Pass".""",
- "send_to": "Witch",
- "restricted_to": "Moderator,Witch",
- }, #
- 10: {"content": "Witch, close your eyes", "send_to": "Moderator", "restricted_to": ""},
- 11: {"content": "Seer, please open your eyes!", "send_to": "Moderator", "restricted_to": ""},
- 12: {
- "content": """Seer, you can check one player's identity. Who are you going to verify its identity tonight?
- Choose only one from the following living options:{living_players}.""",
- "send_to": "Seer",
- "restricted_to": "Moderator,Seer",
- },
- 13: {"content": "Seer, close your eyes", "send_to": "Moderator", "restricted_to": ""},
- # The 1-st daytime
- 14: {
- "content": """It's daytime. Everyone woke up except those who had been killed.""",
- "send_to": "Moderator",
- "restricted_to": "",
- },
- 15: {"content": "{player_current_dead} was killed last night!", "send_to": "Moderator", "restricted_to": ""},
- 16: {
- "content": """Living players: {living_players}, now freely talk about the current situation based on your observation and
- reflection with a few sentences. Decide whether to reveal your identity based on your reflection.""",
- "send_to": "", # send to all to speak in daytime
- "restricted_to": "",
- },
- 17: {
- "content": """Now vote and tell me who you think is the werewolf. Don’t mention your role.
- You only choose one from the following living options please:
- {living_players}. Say ONLY: I vote to eliminate ...""",
- "send_to": "",
- "restricted_to": "",
- },
- 18: {"content": """{player_current_dead} was eliminated.""", "send_to": "Moderator", "restricted_to": ""},
-}
-
-
class WerewolfExtEnv(ExtEnv):
model_config = ConfigDict(arbitrary_types_allowed=True)
players_state: dict[str, tuple[str, RoleState]] = Field(
- default=dict(), description="the player's role type and state by player_name"
+ default_factory=dict, description="the player's role type and state by player_name"
)
round_idx: int = Field(default=0) # the current round
step_idx: int = Field(default=0) # the current step of current round
- eval_step_idx: int = Field(default=0)
+ eval_step_idx: list[int] = Field(default=[])
per_round_steps: int = Field(default=len(STEP_INSTRUCTIONS))
# game global states
@@ -115,13 +32,13 @@ class WerewolfExtEnv(ExtEnv):
special_role_players: list[str] = Field(default=[])
winner: Optional[str] = Field(default=None)
win_reason: Optional[str] = Field(default=None)
- witch_poison_left: int = Field(default=1)
- witch_antidote_left: int = Field(default=1)
+ witch_poison_left: int = Field(default=1, description="should be 1 or 0")
+ witch_antidote_left: int = Field(default=1, description="should be 1 or 0")
# game current round states, a round is from closing your eyes to the next time you close your eyes
- round_hunts: dict[str, str] = Field(default=dict(), description="nighttime wolf hunt result")
+ round_hunts: dict[str, str] = Field(default_factory=dict, description="nighttime wolf hunt result")
round_votes: dict[str, str] = Field(
- default=dict(), description="daytime all players vote result, key=voteer, value=voted one"
+ default_factory=dict, description="daytime all players vote result, key=voter, value=voted one"
)
player_hunted: Optional[str] = Field(default=None)
player_protected: Optional[str] = Field(default=None)
@@ -135,13 +52,69 @@ class WerewolfExtEnv(ExtEnv):
seed: Optional[int] = None,
options: Optional[dict[str, Any]] = None,
) -> tuple[dict[str, Any], dict[str, Any]]:
+ """currently unused"""
pass
def observe(self, obs_params: Optional[BaseEnvObsParams] = None) -> Any:
+ """currently unused"""
pass
- def step(self, action: BaseEnvAction) -> tuple[dict[str, Any], float, bool, bool, dict[str, Any]]:
- pass
+ def _get_obs(self):
+ return {
+ "game_setup": self.game_setup,
+ "step_idx": self.step_idx,
+ "living_players": self.living_players,
+ "werewolf_players": self.werewolf_players, # currently, lack observation isolation
+ "player_hunted": self.player_hunted,
+ "player_current_dead": self.player_current_dead,
+ "witch_poison_left": self.witch_poison_left,
+ "witch_antidote_left": self.witch_antidote_left,
+ "winner": self.winner,
+ "win_reason": self.win_reason,
+ }
+
+ def step(self, action: EnvAction) -> tuple[dict[str, Any], float, bool, bool, dict[str, Any]]:
+ action_type = action.action_type
+ player_name = action.player_name
+ target_player_name = action.target_player_name
+ if action_type == EnvActionType.WOLF_KILL:
+ self.wolf_kill_someone(wolf_name=player_name, player_name=target_player_name)
+ elif action_type == EnvActionType.VOTE_KILL:
+ self.vote_kill_someone(voter_name=player_name, player_name=target_player_name)
+ elif action_type == EnvActionType.WITCH_POISON:
+ self.witch_poison_someone(witch_name=player_name, player_name=target_player_name)
+ elif action_type == EnvActionType.WITCH_SAVE:
+ self.witch_save_someone(witch_name=player_name, player_name=target_player_name)
+ elif action_type == EnvActionType.GUARD_PROTECT:
+ self.guard_protect_someone(guard_name=player_name, player_name=target_player_name)
+ elif action_type == EnvActionType.PROGRESS_STEP:
+ self.progress_step()
+ elif action_type == EnvActionType.NONE:
+ pass
+ else:
+ raise ValueError(f"not supported action_type: {action_type}")
+
+ self.update_game_states()
+ terminated = self._check_game_finish()
+ obs = self._get_obs()
+ return obs, 1.0, terminated, False, {}
+
+ def _check_game_finish(self) -> bool:
+ """return True if game finished else False"""
+ # game's termination condition
+ terminated = False
+ living_werewolf = [p for p in self.werewolf_players if p in self.living_players]
+ living_villagers = [p for p in self.villager_players if p in self.living_players]
+ living_special_roles = [p for p in self.special_role_players if p in self.living_players]
+ if not living_werewolf:
+ self.winner = "good guys"
+ self.win_reason = "werewolves all dead"
+ terminated = True
+ elif not living_villagers or not living_special_roles:
+ self.winner = "werewolf"
+ self.win_reason = "villagers all dead" if not living_villagers else "special roles all dead"
+ terminated = True
+ return terminated
@property
def living_players(self) -> list[str]:
@@ -161,12 +134,12 @@ class WerewolfExtEnv(ExtEnv):
@property
def werewolf_players(self) -> list[str]:
- player_names = self._role_type_players(role_type="Werewolf")
+ player_names = self._role_type_players(role_type=RoleType.WEREWOLF.value)
return player_names
@property
def villager_players(self) -> list[str]:
- player_names = self._role_type_players(role_type="Villager")
+ player_names = self._role_type_players(role_type=RoleType.VILLAGER.value)
return player_names
def _init_players_state(self, players: list["Role"]):
@@ -193,14 +166,14 @@ class WerewolfExtEnv(ExtEnv):
"""init players using different roles' num"""
role_objs = []
for role_obj in role_uniq_objs:
- if str(role_obj) == "Villager":
+ if RoleType.VILLAGER.value in str(role_obj):
role_objs.extend([role_obj] * num_villager)
- elif str(role_obj) == "Werewolf":
+ elif RoleType.WEREWOLF.value in str(role_obj):
role_objs.extend([role_obj] * num_werewolf)
else:
role_objs.append(role_obj)
if shuffle:
- random.shuffle(len(role_objs))
+ random.shuffle(role_objs)
if add_human:
assigned_role_idx = random.randint(0, len(role_objs) - 1)
assigned_role = role_objs[assigned_role_idx]
@@ -233,10 +206,12 @@ class WerewolfExtEnv(ExtEnv):
roletype_state = self.players_state[player_name]
self.players_state[player_name] = (roletype_state[0], state)
- def _check_valid_role(self, player: "Role", role_type: str) -> bool:
- return True if role_type in str(player) else False
+ def _check_valid_role(self, player_name: str, role_type: str) -> bool:
+ roletype_state = self.players_state.get(player_name)
+ return True if roletype_state and role_type in roletype_state[0] else False
def _check_player_continue(self, player_name: str, particular_step: int = -1) -> bool:
+ """to check if can do the operation to the player"""
step_idx = self.step_idx % self.per_round_steps
if particular_step > 0 and step_idx != particular_step: # step no
# particular_step = 18, not daytime vote time, ignore
@@ -253,6 +228,10 @@ class WerewolfExtEnv(ExtEnv):
self.step_idx += 1
return instruction
+ @mark_as_writeable
+ def progress_step(self):
+ self.step_idx += 1
+
@mark_as_readable
def get_players_state(self, player_names: list[str]) -> dict[str, RoleState]:
players_state = {
@@ -263,57 +242,72 @@ class WerewolfExtEnv(ExtEnv):
return players_state
@mark_as_writeable
- def vote_kill_someone(self, voteer: "Role", player_name: str = None):
+ def vote_kill_someone(self, voter_name: str, player_name: str = None):
"""player vote result at daytime
player_name: if it's None, regard as abstaining from voting
"""
- if not self._check_player_continue(voteer.name, particular_step=18): # 18=step no
+ if not self._check_player_continue(voter_name, particular_step=18): # 18=step no
return
- self.round_votes[voteer.name] = player_name
+ self.round_votes[voter_name] = player_name
# check if all living players finish voting, then get the dead one
if list(self.round_votes.keys()) == self.living_players:
voted_all = list(self.round_votes.values()) # TODO in case of tie vote, check who was voted first
voted_all = [item for item in voted_all if item]
- self.player_current_dead = Counter(voted_all).most_common()[0][0]
- self._update_players_state([self.player_current_dead])
+ self.player_current_dead = [Counter(voted_all).most_common()[0][0]]
+ self._update_players_state(self.player_current_dead)
@mark_as_writeable
- def wolf_kill_someone(self, wolf: "Role", player_name: str):
- if not self._check_valid_role(wolf, "Werewolf"):
+ def wolf_kill_someone(self, wolf_name: str, player_name: str):
+ if not self._check_valid_role(wolf_name, RoleType.WEREWOLF.value):
return
- if not self._check_player_continue(wolf.name, particular_step=5): # 5=step no
+ if not self._check_player_continue(wolf_name, particular_step=6): # 5=step no
return
- self.round_hunts[wolf.name] = player_name
- living_werewolf = [p for p in self.werewolf_players if p in self.living_players]
+ self.round_hunts[wolf_name] = player_name
+ # living_werewolf = [p for p in self.werewolf_players if p in self.living_players]
# check if all living wolfs finish hunting, then get the hunted one
- if list(self.round_hunts.keys()) == living_werewolf:
- hunted_all = list(self.round_hunts.values())
- self.player_hunted = Counter(hunted_all).most_common()[0][0]
+ # if list(self.round_hunts.keys()) == living_werewolf:
+ # hunted_all = list(self.round_hunts.values())
+ # self.player_hunted = Counter(hunted_all).most_common()[0][0]
+ self.player_hunted = player_name
- @mark_as_writeable
- def witch_poison_someone(self, witch: "Role", player_name: str = None):
- if not self._check_valid_role(witch, "Witch"):
+ def _witch_poison_or_save_someone(
+ self, witch_name: str, player_name: str = None, state: RoleState = RoleState.POISONED
+ ):
+ if not self._check_valid_role(witch_name, RoleType.WITCH.value):
return
if not self._check_player_continue(player_name):
return
- self._update_players_state([player_name], RoleState.POISONED)
- self.player_poisoned = player_name
+ assert state in [RoleState.POISONED, RoleState.SAVED]
+ self._update_players_state([player_name], state)
+ if state == RoleState.POISONED:
+ self.player_poisoned = player_name
+ self.witch_poison_left -= 1
+ else:
+ # self.player_protected = player_name
+ self.is_hunted_player_saved = True
+ self.witch_antidote_left -= 1
@mark_as_writeable
- def witch_save_someone(self, witch: "Role", player_name: str = None):
- if not self._check_valid_role(witch, "Witch"):
+ def witch_poison_someone(self, witch_name: str, player_name: str = None):
+ self._witch_poison_or_save_someone(witch_name, player_name, RoleState.POISONED)
+
+ @mark_as_writeable
+ def witch_save_someone(self, witch_name: str, player_name: str = None):
+ self._witch_poison_or_save_someone(witch_name, player_name, RoleState.SAVED)
+
+ @mark_as_writeable
+ def guard_protect_someone(self, guard_name: str, player_name: str = None):
+ if not self._check_valid_role(guard_name, RoleType.GUARD.value):
return
if not self._check_player_continue(player_name):
return
-
- self._update_players_state([player_name], RoleState.SAVED)
self.player_protected = player_name
@mark_as_writeable
- def update_game_states(self, memories: list):
+ def update_game_states(self):
step_idx = self.step_idx % self.per_round_steps
if step_idx not in [15, 18] or self.step_idx in self.eval_step_idx:
return
@@ -329,22 +323,12 @@ class WerewolfExtEnv(ExtEnv):
if self.player_poisoned:
self.player_current_dead.append(self.player_poisoned)
- self._update_players_state([self.player_current_dead])
+ self._update_players_state(self.player_current_dead)
# reset
self.player_hunted = None
self.player_protected = None
self.is_hunted_player_saved = False
self.player_poisoned = None
-
- # game's termination condition
- living_werewolf = [p for p in self.werewolf_players if p in self.living_players]
- living_villagers = [p for p in self.villager_players if p in self.living_players]
- living_special_roles = [p for p in self.special_role_players if p in self.living_players]
- if not living_werewolf:
- self.winner = "good guys"
- self.win_reason = "werewolves all dead"
- elif not living_villagers or not living_special_roles:
- self.winner = "werewolf"
- self.win_reason = "villagers all dead" if not living_villagers else "special roles all dead"
- if self.winner is not None:
- self._record_all_experiences() # TODO
+ elif step_idx == 18:
+ # updated use vote_kill_someone
+ pass
diff --git a/metagpt/ext/aflow/benchmark/README.md b/metagpt/ext/aflow/benchmark/README.md
new file mode 100644
index 000000000..4a2464fd1
--- /dev/null
+++ b/metagpt/ext/aflow/benchmark/README.md
@@ -0,0 +1,29 @@
+# Custom Evaluation Function via Benchmark Class
+
+## How to Use
+
+To create a benchmark for a new dataset, follow these steps:
+
+1. Create a new Python file, e.g., `my_dataset_benchmark.py`
+2. Import the base class:
+ ```python
+ from metagpt.ext.aflow.benchmark.benchmark import BaseBenchmark
+ ```
+3. Create a new class that inherits from `BaseBenchmark`:
+ ```python
+ class MyDatasetBenchmark(BaseBenchmark):
+ def __init__(self, name: str, file_path: str, log_path: str):
+ super().__init__(name, file_path, log_path)
+ ```
+4. Implement the required abstract methods:
+ - `evaluate_problem`: Evaluate a single problem
+ - `calculate_score`: Calculate the score for a prediction
+ - `get_result_columns`: Define column names for the results CSV file
+
+5. Override other methods as needed, such as `load_data` or `save_results_to_csv`
+
+## Example
+
+Refer to the `DROPBenchmark` class in the `drop.py` file for an example of how to implement a benchmark for a specific dataset.
+
+By following these guidelines, you can easily create custom benchmark evaluations for new datasets.
diff --git a/metagpt/ext/aflow/benchmark/benchmark.py b/metagpt/ext/aflow/benchmark/benchmark.py
new file mode 100644
index 000000000..b5692f01e
--- /dev/null
+++ b/metagpt/ext/aflow/benchmark/benchmark.py
@@ -0,0 +1,104 @@
+import asyncio
+import json
+import os
+from abc import ABC, abstractmethod
+from datetime import datetime
+from pathlib import Path
+from typing import Any, Callable, List, Tuple
+
+import aiofiles
+import pandas as pd
+from tqdm.asyncio import tqdm_asyncio
+
+from metagpt.logs import logger
+from metagpt.utils.common import write_json_file
+
+
+class BaseBenchmark(ABC):
+ def __init__(self, name: str, file_path: str, log_path: str):
+ self.name = name
+ self.file_path = file_path
+ self.log_path = log_path
+
+ PASS = "PASS"
+ FAIL = "FAIL"
+
+ async def load_data(self, specific_indices: List[int] = None) -> List[dict]:
+ data = []
+ async with aiofiles.open(self.file_path, mode="r", encoding="utf-8") as file:
+ async for line in file:
+ data.append(json.loads(line))
+ if specific_indices is not None:
+ filtered_data = [data[i] for i in specific_indices if i < len(data)]
+ return filtered_data
+ return data
+
+ def save_results_to_csv(self, results: List[Tuple[Any, ...]], columns: List[str]):
+ df = pd.DataFrame(results, columns=columns)
+ avg_score = df["score"].mean()
+ t_cost = df["cost"].max()
+ a_cost = t_cost / len(df) if len(df) > 0 else 0
+ current_time = datetime.now().strftime("%Y%m%d_%H%M%S")
+ filename = f"{avg_score:.5f}_{current_time}.csv"
+ output_file = os.path.join(self.log_path, filename)
+ df.to_csv(output_file, index=False)
+ logger.info(f"Results saved to {output_file}")
+ return avg_score, a_cost, t_cost
+
+ def log_mismatch(
+ self,
+ problem: str,
+ expected_output: Any,
+ prediction: str,
+ extracted_output: Any,
+ extract_answer_code: str = "None",
+ ):
+ log_data = {
+ "question": problem,
+ "right_answer": expected_output,
+ "model_output": prediction,
+ "extracted_output": extracted_output,
+ "extract_answer_code": extract_answer_code,
+ }
+ log_file = Path(self.log_path) / "log.json"
+ if log_file.exists():
+ with log_file.open("r", encoding="utf-8") as f:
+ try:
+ data = json.load(f)
+ except json.JSONDecodeError:
+ data = []
+ else:
+ data = []
+ data.append(log_data)
+ write_json_file(log_file, data, encoding="utf-8", indent=4)
+
+ @abstractmethod
+ async def evaluate_problem(self, problem: dict, graph: Callable) -> Tuple[Any, ...]:
+ pass
+
+ @abstractmethod
+ def calculate_score(self, expected_output: Any, prediction: Any) -> Tuple[float, Any]:
+ pass
+
+ @abstractmethod
+ def get_result_columns(self) -> List[str]:
+ pass
+
+ async def evaluate_all_problems(self, data: List[dict], graph: Callable, max_concurrent_tasks: int = 50):
+ semaphore = asyncio.Semaphore(max_concurrent_tasks)
+
+ async def sem_evaluate(problem):
+ async with semaphore:
+ return await self.evaluate_problem(problem, graph)
+
+ tasks = [sem_evaluate(problem) for problem in data]
+ return await tqdm_asyncio.gather(*tasks, desc=f"Evaluating {self.name} problems", total=len(data))
+
+ async def run_evaluation(self, graph: Callable, va_list: List[int], max_concurrent_tasks: int = 50):
+ data = await self.load_data(va_list)
+ results = await self.evaluate_all_problems(data, graph, max_concurrent_tasks)
+ columns = self.get_result_columns()
+ average_score, average_cost, total_cost = self.save_results_to_csv(results, columns)
+ logger.info(f"Average score on {self.name} dataset: {average_score:.5f}")
+ logger.info(f"Total Cost: {total_cost:.5f}")
+ return average_score, average_cost, total_cost
diff --git a/metagpt/ext/aflow/benchmark/drop.py b/metagpt/ext/aflow/benchmark/drop.py
new file mode 100644
index 000000000..3cec5795f
--- /dev/null
+++ b/metagpt/ext/aflow/benchmark/drop.py
@@ -0,0 +1,83 @@
+import re
+import string
+from collections import Counter
+from typing import Callable, List, Tuple
+
+from tenacity import retry, retry_if_exception_type, stop_after_attempt, wait_fixed
+
+from metagpt.ext.aflow.benchmark.benchmark import BaseBenchmark
+from metagpt.logs import logger
+
+
+class DROPBenchmark(BaseBenchmark):
+ def __init__(self, name: str, file_path: str, log_path: str):
+ super().__init__(name, file_path, log_path)
+
+ def normalize_answer(self, s: str) -> List[str]:
+ """
+ Normalize answers for evaluation.
+ """
+
+ def remove_articles(text):
+ return re.sub(r"\b(a|an|the)\b", " ", text)
+
+ def white_space_fix(text):
+ return " ".join(text.split())
+
+ def remove_punc(text):
+ exclude = set(string.punctuation)
+ return "".join(ch for ch in text if ch not in exclude)
+
+ def lower(text):
+ return text.lower()
+
+ return white_space_fix(remove_articles(remove_punc(lower(s))))
+
+ def calculate_score(self, ground_truth: str, prediction: str) -> Tuple[float, str]:
+ """
+ Compute the F1 score between prediction and ground truth answers.
+ """
+ prediction_tokens = self.normalize_answer(prediction).split()
+ ground_truth_tokens = self.normalize_answer(ground_truth).split()
+ common = Counter(prediction_tokens) & Counter(ground_truth_tokens)
+ num_same = sum(common.values())
+ if num_same == 0:
+ return 0, prediction
+ precision = 1.0 * num_same / len(prediction_tokens)
+ recall = 1.0 * num_same / len(ground_truth_tokens)
+ f1 = (2 * precision * recall) / (precision + recall)
+ return f1, prediction
+
+ @retry(stop=stop_after_attempt(5), wait=wait_fixed(1), retry=retry_if_exception_type(Exception), reraise=True)
+ async def _generate_output(self, graph, input_text):
+ return await graph(input_text)
+
+ async def evaluate_problem(self, problem: dict, graph: Callable) -> Tuple[str, str, str, float, float]:
+ input_text = problem["context"]
+ expected_output = problem["ref_text"]
+ answers = expected_output.split("|")
+
+ try:
+ output, cost = await self._generate_output(graph, input_text)
+ f1_scores = []
+
+ for answer in answers:
+ if answer.strip() != "":
+ output_parts = output.split("|")
+ for output_part in output_parts:
+ f1_score, _ = self.calculate_score(answer, output_part)
+ f1_scores.append(f1_score)
+
+ uni_score = max(f1_scores)
+
+ if uni_score < 0.3:
+ self.log_mismatch(input_text, expected_output, output, output)
+
+ return input_text, output, expected_output, uni_score, cost
+
+ except Exception as e:
+ logger.info(f"Maximum retries reached. Skipping this sample. Error: {e}")
+ return input_text, str(e), expected_output, 0.0, 0.0
+
+ def get_result_columns(self) -> List[str]:
+ return ["inputs", "prediction", "expected_output", "score", "cost"]
diff --git a/metagpt/ext/aflow/benchmark/gsm8k.py b/metagpt/ext/aflow/benchmark/gsm8k.py
new file mode 100644
index 000000000..51979c0c5
--- /dev/null
+++ b/metagpt/ext/aflow/benchmark/gsm8k.py
@@ -0,0 +1,57 @@
+# -*- coding: utf-8 -*-
+# @Date :
+# @Author : all
+# @Desc : test on gsm8k
+import re
+from typing import Callable, List, Optional, Tuple
+
+from tenacity import retry, retry_if_exception_type, stop_after_attempt, wait_fixed
+
+from metagpt.ext.aflow.benchmark.benchmark import BaseBenchmark
+from metagpt.logs import logger
+
+
+class GSM8KBenchmark(BaseBenchmark):
+ def __init__(self, name: str, file_path: str, log_path: str):
+ super().__init__(name, file_path, log_path)
+
+ def extract_number(self, text: str) -> Optional[float]:
+ matches = re.findall(r"[-+]?\d+(?:,\d{3})*(?:\.\d+)?|\d+\.\d+", str(text))
+ if matches:
+ last_number = matches[-1].replace(",", "")
+ try:
+ return float(last_number)
+ except ValueError:
+ return None
+ else:
+ return None
+
+ def calculate_score(self, expected_output: float, prediction: float) -> Tuple[float, float]:
+ if prediction is None:
+ return 0.0, prediction
+ return 1.0 if abs(expected_output - prediction) <= 1e-6 else 0.0, prediction
+
+ @retry(stop=stop_after_attempt(5), wait=wait_fixed(1), retry=retry_if_exception_type(Exception), reraise=True)
+ async def _generate_output(self, graph, input_text):
+ return await graph(input_text)
+
+ async def evaluate_problem(self, problem: dict, graph: Callable) -> Tuple[str, str, float, float, float]:
+ input_text = problem["question"]
+ expected_output = self.extract_number(problem["answer"])
+
+ try:
+ output, cost = await self._generate_output(graph, input_text)
+ predicted_number = self.extract_number(output)
+ score, extracted_output = self.calculate_score(expected_output, predicted_number)
+
+ if score == 0:
+ self.log_mismatch(input_text, expected_output, output, extracted_output)
+
+ return input_text, output, expected_output, score, cost
+
+ except Exception as e:
+ logger.info(f"Maximum retries reached. Skipping this sample. Error: {e}")
+ return input_text, str(e), expected_output, 0.0, 0.0
+
+ def get_result_columns(self) -> List[str]:
+ return ["question", "prediction", "expected_output", "score", "cost"]
diff --git a/metagpt/ext/aflow/benchmark/hotpotqa.py b/metagpt/ext/aflow/benchmark/hotpotqa.py
new file mode 100644
index 000000000..b3bafe22b
--- /dev/null
+++ b/metagpt/ext/aflow/benchmark/hotpotqa.py
@@ -0,0 +1,71 @@
+import re
+import string
+from collections import Counter
+from typing import Callable, List, Tuple
+
+from tenacity import retry, retry_if_exception_type, stop_after_attempt, wait_fixed
+
+from metagpt.ext.aflow.benchmark.benchmark import BaseBenchmark
+from metagpt.logs import logger
+
+
+class HotpotQABenchmark(BaseBenchmark):
+ def __init__(self, name: str, file_path: str, log_path: str):
+ super().__init__(name, file_path, log_path)
+
+ def normalize_answer(self, s: str) -> str:
+ def remove_articles(text):
+ return re.sub(r"\b(a|an|the)\b", " ", text)
+
+ def white_space_fix(text):
+ return " ".join(text.split())
+
+ def remove_punc(text):
+ exclude = set(string.punctuation)
+ return "".join(ch for ch in text if ch not in exclude)
+
+ def lower(text):
+ return text.lower()
+
+ return white_space_fix(remove_articles(remove_punc(lower(s))))
+
+ def calculate_score(self, ground_truth: str, prediction: str) -> Tuple[float, str]:
+ prediction_tokens = self.normalize_answer(prediction).split()
+ ground_truth_tokens = self.normalize_answer(ground_truth).split()
+ common = Counter(prediction_tokens) & Counter(ground_truth_tokens)
+ num_same = sum(common.values())
+ if num_same == 0:
+ return 0, prediction
+ precision = 1.0 * num_same / len(prediction_tokens)
+ recall = 1.0 * num_same / len(ground_truth_tokens)
+ f1 = (2 * precision * recall) / (precision + recall)
+ return f1, prediction
+
+ @retry(stop=stop_after_attempt(5), wait=wait_fixed(1), retry=retry_if_exception_type(Exception), reraise=True)
+ async def _generate_output(self, graph, input_text):
+ return await graph(input_text)
+
+ async def evaluate_problem(self, problem: dict, graph: Callable) -> Tuple[str, str, str, str, float, float]:
+ input_text = problem["question"]
+ expected_output = problem["answer"]
+ paragraphs = [item[1] for item in problem["context"] if isinstance(item[1], list)]
+ context_str = "\n".join(" ".join(paragraph) for paragraph in paragraphs)
+ inputs = f"Context: {context_str}\n\nQuestion: {input_text}\n\nAnswer:"
+
+ try:
+ output, cost = await self._generate_output(graph, inputs)
+ score, extracted_output = self.calculate_score(expected_output, output)
+
+ if (
+ score < 0.3
+ ): # We set the threshold for collecting incorrect questions to 0.3, as F1 Score cannot be simply judged using 0-1
+ self.log_mismatch(input_text, expected_output, output, extracted_output)
+
+ return input_text, context_str, output, expected_output, score, cost
+
+ except Exception as e:
+ logger.info(f"Maximum retries reached. Skipping this sample. Error: {e}")
+ return input_text, context_str, str(e), expected_output, 0.0, 0.0
+
+ def get_result_columns(self) -> List[str]:
+ return ["question", "context", "prediction", "expected_output", "score", "cost"]
diff --git a/metagpt/ext/aflow/benchmark/humaneval.py b/metagpt/ext/aflow/benchmark/humaneval.py
new file mode 100644
index 000000000..b54add260
--- /dev/null
+++ b/metagpt/ext/aflow/benchmark/humaneval.py
@@ -0,0 +1,151 @@
+import asyncio
+import threading
+import time
+from typing import Any, Callable, Dict, List, Optional, Tuple
+
+from tenacity import retry, retry_if_exception_type, stop_after_attempt, wait_fixed
+
+from metagpt.ext.aflow.benchmark.benchmark import BaseBenchmark
+from metagpt.logs import logger
+from metagpt.utils.sanitize import sanitize
+
+
+class HumanEvalBenchmark(BaseBenchmark):
+ def __init__(self, name: str, file_path: str, log_path: str):
+ super().__init__(name, file_path, log_path)
+
+ class TimeoutError(Exception):
+ pass
+
+ def run_with_timeout(self, func, args, timeout):
+ result = []
+ stop_event = threading.Event()
+
+ def target():
+ try:
+ result.append(func(*args))
+ except Exception as e:
+ result.append(e)
+ finally:
+ stop_event.set()
+
+ thread = threading.Thread(target=target)
+ thread.start()
+ is_timeout = not stop_event.wait(timeout)
+
+ if is_timeout:
+ raise self.TimeoutError("Function execution timed out")
+
+ if not result:
+ return None
+ if isinstance(result[0], Exception):
+ raise result[0]
+ return result[0]
+
+ def check_solution(self, solution, test, entry_point):
+ solution = sanitize(code=solution, entrypoint=entry_point)
+ try:
+ global_dict = {
+ "math": __import__("math"),
+ "hashlib": __import__("hashlib"),
+ "re": __import__("re"),
+ "List": List,
+ "Dict": Dict,
+ "Tuple": Tuple,
+ "Optional": Optional,
+ "Any": Any,
+ }
+
+ # Add handling for special cases
+ if entry_point == "decode_cyclic":
+ solution = (
+ '\n\ndef encode_cyclic(s: str):\n """\n returns encoded string by cycling groups of three characters.\n """\n # split string to groups. Each of length 3.\n groups = [s[(3 * i):min((3 * i + 3), len(s))] for i in range((len(s) + 2) // 3)]\n # cycle elements in each group. Unless group has fewer elements than 3.\n groups = [(group[1:] + group[0]) if len(group) == 3 else group for group in groups]\n return "".join(groups)'
+ + "\n\n"
+ + solution
+ )
+ elif entry_point == "decode_shift":
+ solution = (
+ '\n\ndef encode_shift(s: str):\n """\n returns encoded string by shifting every character by 5 in the alphabet.\n """\n return "".join([chr(((ord(ch) + 5 - ord("a")) % 26) + ord("a")) for ch in s])\n\n\n'
+ + solution
+ )
+ elif entry_point == "find_zero":
+ solution = (
+ "\n\ndef poly(xs: list, x: float):\n return sum(coeff * (x ** i) for i, coeff in enumerate(xs))\n\n"
+ + solution
+ )
+
+ exec(solution, global_dict)
+
+ if entry_point not in global_dict:
+ raise ValueError(f"Function {entry_point} is not defined in the solution.")
+
+ exec(test, global_dict)
+
+ check = global_dict["check"]
+
+ result = self.run_with_timeout(check, (global_dict[entry_point],), 15)
+
+ if result is None:
+ result = (self.PASS, "The solution passed all test cases.")
+
+ except self.TimeoutError:
+ result = (
+ self.FAIL,
+ "Execution timed out. Please check if your solution contains infinite loops or overly time-consuming operations.",
+ )
+ except Exception as e:
+ error_message = f"Error: {str(e)}.\n Solution: {solution}.\n Test: {test}"
+ result = (self.FAIL, error_message)
+
+ with open("error.log", "a", encoding="utf-8") as log_file:
+ log_file.write(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - {error_message}\n")
+
+ return result
+
+ @retry(stop=stop_after_attempt(5), wait=wait_fixed(1), retry=retry_if_exception_type(Exception), reraise=True)
+ async def _generate_output(self, graph, prompt, entry_point):
+ # Generate output with a timeout of 60 seconds
+ return await asyncio.wait_for(graph(prompt, entry_point), timeout=60)
+
+ async def evaluate_problem(self, data: dict, graph: Callable) -> Tuple[str, str, str, float, float]:
+ input_text = data["prompt"]
+ expected_output = (
+ "\nCorrect Solution:\ndef "
+ + data["entry_point"]
+ + "(params you should put here):"
+ + "\n\n"
+ + data["canonical_solution"]
+ )
+
+ try:
+ # Generate prediction using the graph function
+ prediction, cost = await self._generate_output(graph, input_text, data["entry_point"])
+
+ # Check the solution
+ ret = self.check_solution(prediction, data["test"], data["entry_point"])
+ test_case_details = ret[1]
+ expected_output = test_case_details + expected_output
+
+ # Calculate score based on the check result
+ score = 1.0 if ret[0] == self.PASS else 0.0
+
+ # Log mismatch if the score is 0
+ if score == 0:
+ self.log_mismatch(input_text, expected_output, prediction, score)
+
+ return input_text, prediction, expected_output, score, cost
+
+ except asyncio.TimeoutError:
+ logger.info("Timeout error. Skipping this sample.")
+ return input_text, "Timeout", expected_output, 0.0, 0.0
+
+ except Exception as e:
+ logger.info(f"Maximum retries reached. Skipping this sample. Error: {e}")
+ return input_text, str(e), expected_output, 0.0, 0.0
+
+ def calculate_score(self, expected_output: str, prediction: str) -> Tuple[float, str]:
+ # The scoring logic for HumanEval is already implemented in evaluate_problem, this is just to conform to the interface
+ return 0.0, prediction
+
+ def get_result_columns(self) -> List[str]:
+ return ["inputs", "prediction", "expected_output", "score", "cost"]
diff --git a/metagpt/ext/aflow/benchmark/math.py b/metagpt/ext/aflow/benchmark/math.py
new file mode 100644
index 000000000..07b0612d0
--- /dev/null
+++ b/metagpt/ext/aflow/benchmark/math.py
@@ -0,0 +1,137 @@
+import inspect
+import re
+from math import isclose
+from typing import Any, Callable, List, Tuple
+
+import regex
+from sympy import N, simplify
+from sympy.parsing.latex import parse_latex
+from sympy.parsing.sympy_parser import parse_expr
+from tenacity import retry, retry_if_exception_type, stop_after_attempt, wait_fixed
+
+from metagpt.ext.aflow.benchmark.benchmark import BaseBenchmark
+from metagpt.logs import logger
+
+
+class MATHBenchmark(BaseBenchmark):
+ def __init__(self, name: str, file_path: str, log_path: str):
+ super().__init__(name, file_path, log_path)
+
+ def extract_model_answer(self, text: str) -> str:
+ pattern = r"\\boxed{((?:[^{}]|{[^{}]*})*)}"
+ boxed_matches = re.findall(pattern, text, re.DOTALL)
+ if boxed_matches:
+ return boxed_matches[-1].strip()
+
+ sentence_end_pattern = r"(? Tuple[int, str]:
+ expected_answer = self.extract_model_answer(expected_output)
+ predicted_answer = self.extract_model_answer(prediction)
+
+ if self.math_equal(predicted_answer, expected_answer):
+ return 1, predicted_answer
+ else:
+ return 0, predicted_answer
+
+ def math_equal(self, prediction: Any, reference: Any) -> bool:
+ if str(prediction) == str(reference):
+ return True
+
+ try:
+ if self.is_digit(prediction) and self.is_digit(reference):
+ prediction = self.parse_digits(prediction)
+ reference = self.parse_digits(reference)
+ return isclose(prediction, reference, abs_tol=1e-3)
+ except:
+ pass
+
+ try:
+ return self.symbolic_equal(prediction, reference)
+ except:
+ pass
+
+ return False
+
+ def is_digit(self, num):
+ return self.parse_digits(num) is not None
+
+ def parse_digits(self, num):
+ num = regex.sub(",", "", str(num))
+ try:
+ return float(num)
+ except:
+ if num.endswith("%"):
+ num = num[:-1]
+ if num.endswith("\\"):
+ num = num[:-1]
+ try:
+ return float(num) / 100
+ except:
+ pass
+ return None
+
+ def symbolic_equal(self, a, b):
+ def _parse(s):
+ for f in [parse_latex, parse_expr]:
+ try:
+ return f(s)
+ except:
+ pass
+ return s
+
+ a = _parse(a)
+ b = _parse(b)
+
+ try:
+ if simplify(a - b) == 0:
+ return True
+ except:
+ pass
+
+ try:
+ if isclose(N(a), N(b), abs_tol=1e-3):
+ return True
+ except:
+ pass
+ return False
+
+ def get_function_code(self, func):
+ try:
+ source_code = inspect.getsource(func)
+ return source_code
+ except OSError:
+ return "no code"
+
+ @retry(stop=stop_after_attempt(5), wait=wait_fixed(1), retry=retry_if_exception_type(Exception), reraise=True)
+ async def _generate_output(self, graph, input_text):
+ return await graph(input_text)
+
+ async def evaluate_problem(self, problem: dict, graph: Callable) -> Tuple[str, str, str, int, float]:
+ input_text = problem["problem"]
+ expected_output = problem["solution"]
+
+ try:
+ output, cost = await self._generate_output(graph, input_text)
+ uni_score, extracted_output = self.calculate_score(expected_output, output)
+
+ if uni_score == 0:
+ self.log_mismatch(
+ input_text,
+ expected_output,
+ output,
+ extracted_output,
+ extract_answer_code=self.get_function_code(self.extract_model_answer),
+ )
+
+ return input_text, output, expected_output, uni_score, cost
+
+ except Exception as e:
+ logger.info(f"Maximum retries reached. Skipping this sample. Error: {e}")
+ return input_text, str(e), expected_output, 0.0, 0.0
+
+ def get_result_columns(self) -> List[str]:
+ return ["question", "prediction", "expected_output", "score", "cost"]
diff --git a/metagpt/ext/aflow/benchmark/mbpp.py b/metagpt/ext/aflow/benchmark/mbpp.py
new file mode 100644
index 000000000..c3628b024
--- /dev/null
+++ b/metagpt/ext/aflow/benchmark/mbpp.py
@@ -0,0 +1,121 @@
+import threading
+import time
+from typing import Any, Callable, Dict, List, Optional, Tuple
+
+from tenacity import retry, retry_if_exception_type, stop_after_attempt, wait_fixed
+
+from metagpt.ext.aflow.benchmark.benchmark import BaseBenchmark
+from metagpt.logs import logger
+from metagpt.utils.sanitize import sanitize
+
+
+class MBPPBenchmark(BaseBenchmark):
+ def __init__(self, name: str, file_path: str, log_path: str):
+ super().__init__(name, file_path, log_path)
+
+ class TimeoutError(Exception):
+ pass
+
+ def run_with_timeout(self, func, timeout):
+ result = []
+ stop_event = threading.Event()
+
+ def target():
+ try:
+ result.append(func())
+ except Exception as e:
+ result.append(e)
+ finally:
+ stop_event.set()
+
+ thread = threading.Thread(target=target)
+ thread.start()
+ is_timeout = not stop_event.wait(timeout)
+
+ if is_timeout:
+ raise self.TimeoutError("Function execution timed out")
+
+ if not result:
+ return None
+ if isinstance(result[0], Exception):
+ raise result[0]
+ return result[0]
+
+ def check_solution(self, solution, test, entry_point):
+ solution = sanitize(code=solution, entrypoint=entry_point)
+ try:
+ global_dict = {
+ "math": __import__("math"),
+ "hashlib": __import__("hashlib"),
+ "re": __import__("re"),
+ "List": List,
+ "Dict": Dict,
+ "Tuple": Tuple,
+ "Optional": Optional,
+ "Any": Any,
+ }
+
+ exec(solution, global_dict)
+
+ if entry_point not in global_dict:
+ raise ValueError(f"Function {entry_point} is not defined in the solution.")
+
+ exec(test, global_dict)
+
+ check = global_dict["check"]
+
+ result = self.run_with_timeout(check, 15)
+
+ if result is None:
+ result = (self.PASS, "The solution passed all test cases.")
+
+ except self.TimeoutError:
+ result = (
+ self.FAIL,
+ "Execution timed out. Please check if your solution contains infinite loops or overly time-consuming operations.",
+ )
+ except Exception as e:
+ error_message = f"Error: {str(e)}.\n Solution: {solution}.\n Test: {test}"
+ result = (self.FAIL, error_message)
+
+ with open("error.log", "a", encoding="utf-8") as log_file:
+ log_file.write(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - {error_message}\n")
+
+ return result
+
+ @retry(stop=stop_after_attempt(5), wait=wait_fixed(1), retry=retry_if_exception_type(Exception), reraise=True)
+ async def _generate_output(self, graph, prompt, entry_point):
+ return await graph(prompt, entry_point)
+
+ async def evaluate_problem(self, data: dict, graph: Callable) -> Tuple[str, str, str, float, float]:
+ input_text = data["prompt"]
+ expected_output = "\nCorrect Solution:\ndef " + data["code"]
+
+ try:
+ # Generate prediction using the graph function
+ prediction, cost = await self._generate_output(graph, input_text, data["entry_point"])
+
+ # Check the solution
+ ret = self.check_solution(prediction, data["test"], data["entry_point"])
+ test_case_details = ret[1]
+ expected_output = test_case_details + "\nCorrect Solution:" + data["code"]
+
+ # Calculate score based on the check result
+ score = 1.0 if ret[0] == self.PASS else 0.0
+
+ # Log mismatch if the score is 0
+ if score == 0:
+ self.log_mismatch(input_text, expected_output, prediction, score)
+
+ return input_text, prediction, expected_output, score, cost
+
+ except Exception as e:
+ logger.info(f"Maximum retries reached. Skipping this sample. Error: {e}")
+ return input_text, str(e), expected_output, 0.0, 0.0
+
+ def calculate_score(self, expected_output: str, prediction: str) -> Tuple[float, str]:
+ # The scoring logic for MBPP is already implemented in evaluate_problem, this is just to conform to the interface
+ return 0.0, prediction
+
+ def get_result_columns(self) -> List[str]:
+ return ["inputs", "prediction", "expected_output", "score", "cost"]
diff --git a/metagpt/ext/aflow/benchmark/utils.py b/metagpt/ext/aflow/benchmark/utils.py
new file mode 100644
index 000000000..846101bc0
--- /dev/null
+++ b/metagpt/ext/aflow/benchmark/utils.py
@@ -0,0 +1,67 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+"""
+@Time : 2024/7/24 16:37
+@Author : didi
+@File : utils.py
+"""
+
+import json
+import os
+
+import numpy as np
+
+from metagpt.utils.common import read_json_file, write_json_file
+
+
+def generate_random_indices(n, n_samples, test=False):
+ """
+ Generate random indices
+ """
+
+ def _set_seed(seed=42):
+ np.random.seed(seed)
+
+ _set_seed()
+ indices = np.arange(n)
+ np.random.shuffle(indices)
+ if test:
+ return indices[n_samples:]
+ else:
+ return indices[:n_samples]
+
+
+def split_data_set(file_path, samples, test=False):
+ data = []
+
+ with open(file_path, "r") as file:
+ for line in file:
+ data.append(json.loads(line))
+ random_indices = generate_random_indices(len(data), samples, test)
+ data = [data[i] for i in random_indices]
+ return data
+
+
+def log_mismatch(problem, expected_output, prediction, predicted_number, path):
+ log_data = {
+ "question": problem,
+ "right_answer": expected_output,
+ "model_output": prediction,
+ "extracted_output": predicted_number,
+ }
+
+ log_file = os.path.join(path, "log.json")
+
+ # Check if the log file already exists
+ if os.path.exists(log_file):
+ # If it exists, load the existing log data
+ data = read_json_file(log_file)
+ else:
+ # If it does not exist, create a new log list
+ data = []
+
+ # Add the new log entry
+ data.append(log_data)
+
+ # Write the data back to log.json file
+ write_json_file(log_file, data, encoding="utf-8", indent=4)
diff --git a/metagpt/ext/aflow/data/download_data.py b/metagpt/ext/aflow/data/download_data.py
new file mode 100644
index 000000000..a3aa2774c
--- /dev/null
+++ b/metagpt/ext/aflow/data/download_data.py
@@ -0,0 +1,79 @@
+# -*- coding: utf-8 -*-
+# @Date : 2024-10-20
+# @Author : MoshiQAQ & didi
+# @Desc : Download and extract dataset files
+
+import os
+import tarfile
+from typing import Dict
+
+import requests
+from tqdm import tqdm
+
+from metagpt.logs import logger
+
+
+def download_file(url: str, filename: str) -> None:
+ """Download a file from the given URL and show progress."""
+ response = requests.get(url, stream=True)
+ total_size = int(response.headers.get("content-length", 0))
+ block_size = 1024
+ progress_bar = tqdm(total=total_size, unit="iB", unit_scale=True)
+
+ with open(filename, "wb") as file:
+ for data in response.iter_content(block_size):
+ size = file.write(data)
+ progress_bar.update(size)
+ progress_bar.close()
+
+
+def extract_tar_gz(filename: str, extract_path: str) -> None:
+ """Extract a tar.gz file to the specified path."""
+ with tarfile.open(filename, "r:gz") as tar:
+ tar.extractall(path=extract_path)
+
+
+def process_dataset(url: str, filename: str, extract_path: str) -> None:
+ """Download, extract, and clean up a dataset."""
+ logger.info(f"Downloading {filename}...")
+ download_file(url, filename)
+
+ logger.info(f"Extracting {filename}...")
+ extract_tar_gz(filename, extract_path)
+
+ logger.info(f"{filename} download and extraction completed.")
+
+ os.remove(filename)
+ logger.info(f"Removed {filename}")
+
+
+# Define the datasets to be downloaded
+# Users can modify this list to choose which datasets to download
+datasets_to_download: Dict[str, Dict[str, str]] = {
+ "datasets": {
+ "url": "https://drive.google.com/uc?export=download&id=1DNoegtZiUhWtvkd2xoIuElmIi4ah7k8e",
+ "filename": "aflow_data.tar.gz",
+ "extract_path": "metagpt/ext/aflow/data",
+ },
+ "results": {
+ "url": "https://drive.google.com/uc?export=download&id=1Sr5wjgKf3bN8OC7G6cO3ynzJqD4w6_Dv",
+ "filename": "result.tar.gz",
+ "extract_path": "metagpt/ext/aflow/data/results",
+ },
+ "initial_rounds": {
+ "url": "https://drive.google.com/uc?export=download&id=1UBoW4WBWjX2gs4I_jq3ALdXeLdwDJMdP",
+ "filename": "initial_rounds.tar.gz",
+ "extract_path": "metagpt/ext/aflow/scripts/optimized",
+ },
+}
+
+
+def download(required_datasets, if_first_download: bool = True):
+ """Main function to process all selected datasets"""
+ if if_first_download:
+ for dataset_name in required_datasets:
+ dataset = datasets_to_download[dataset_name]
+ extract_path = dataset["extract_path"]
+ process_dataset(dataset["url"], dataset["filename"], extract_path)
+ else:
+ logger.info("Skip downloading datasets")
diff --git a/metagpt/ext/aflow/scripts/evaluator.py b/metagpt/ext/aflow/scripts/evaluator.py
new file mode 100644
index 000000000..34bdcd9fc
--- /dev/null
+++ b/metagpt/ext/aflow/scripts/evaluator.py
@@ -0,0 +1,63 @@
+# -*- coding: utf-8 -*-
+# @Date : 8/23/2024 10:00 AM
+# @Author : all
+# @Desc : Evaluation for different datasets
+
+from typing import Dict, Literal, Tuple
+
+from metagpt.ext.aflow.benchmark.benchmark import BaseBenchmark
+from metagpt.ext.aflow.benchmark.drop import DROPBenchmark
+from metagpt.ext.aflow.benchmark.gsm8k import GSM8KBenchmark
+from metagpt.ext.aflow.benchmark.hotpotqa import HotpotQABenchmark
+from metagpt.ext.aflow.benchmark.humaneval import HumanEvalBenchmark
+from metagpt.ext.aflow.benchmark.math import MATHBenchmark
+from metagpt.ext.aflow.benchmark.mbpp import MBPPBenchmark
+
+# If you want to customize tasks, add task types here and provide evaluation functions, just like the ones given above
+DatasetType = Literal["HumanEval", "MBPP", "GSM8K", "MATH", "HotpotQA", "DROP"]
+
+
+class Evaluator:
+ """
+ Complete the evaluation for different datasets here
+ """
+
+ def __init__(self, eval_path: str):
+ self.eval_path = eval_path
+ self.dataset_configs: Dict[DatasetType, BaseBenchmark] = {
+ "GSM8K": GSM8KBenchmark,
+ "MATH": MATHBenchmark,
+ "HumanEval": HumanEvalBenchmark,
+ "HotpotQA": HotpotQABenchmark,
+ "MBPP": MBPPBenchmark,
+ "DROP": DROPBenchmark,
+ }
+
+ async def graph_evaluate(
+ self, dataset: DatasetType, graph, params: dict, path: str, is_test: bool = False
+ ) -> Tuple[float, float, float]:
+ if dataset not in self.dataset_configs:
+ raise ValueError(f"Unsupported dataset: {dataset}")
+
+ data_path = self._get_data_path(dataset, is_test)
+ benchmark_class = self.dataset_configs[dataset]
+ benchmark = benchmark_class(name=dataset, file_path=data_path, log_path=path)
+
+ # Use params to configure the graph and benchmark
+ configured_graph = await self._configure_graph(dataset, graph, params)
+ if is_test:
+ va_list = None # For test data, generally use None to test all
+ else:
+ va_list = None # Use None to test all Validation data, or set va_list (e.g., [1, 2, 3]) to use partial data
+ return await benchmark.run_evaluation(configured_graph, va_list)
+
+ async def _configure_graph(self, dataset, graph, params: dict):
+ # Here you can configure the graph based on params
+ # For example: set LLM configuration, dataset configuration, etc.
+ dataset_config = params.get("dataset", {})
+ llm_config = params.get("llm_config", {})
+ return graph(name=dataset, llm_config=llm_config, dataset=dataset_config)
+
+ def _get_data_path(self, dataset: DatasetType, test: bool) -> str:
+ base_path = f"metagpt/ext/aflow/data/{dataset.lower()}"
+ return f"{base_path}_test.jsonl" if test else f"{base_path}_validate.jsonl"
diff --git a/metagpt/ext/aflow/scripts/interface.py b/metagpt/ext/aflow/scripts/interface.py
new file mode 100644
index 000000000..46cdbdabf
--- /dev/null
+++ b/metagpt/ext/aflow/scripts/interface.py
@@ -0,0 +1,98 @@
+# -*- coding: utf-8 -*-
+# @Date : 2024-03-21
+# @Author : Your Name
+# @Desc : Interface for AFLOW
+
+import asyncio
+import importlib.util
+import sys
+from pathlib import Path
+from typing import Optional, Tuple
+
+from metagpt.configs.models_config import ModelsConfig
+from metagpt.ext.aflow.scripts.evaluator import DatasetType
+from metagpt.ext.aflow.scripts.optimizer_utils.data_utils import DataUtils
+from metagpt.logs import logger
+
+
+def load_best_round(dataset: str, optimized_path: str = "metagpt/ext/aflow/scripts/optimized") -> int:
+ """加载最佳表现的轮次"""
+ data_utils = DataUtils(f"{optimized_path}/{dataset}")
+
+ # 使用get_top_rounds获取得分最高的轮次
+ top_rounds = data_utils.get_top_rounds(sample=2, mode="Graph")
+ if not top_rounds[1]:
+ return 1
+
+ return top_rounds[1]["round"]
+
+
+def load_workflow_class(graph_path: str):
+ """动态加载工作流类"""
+ spec = importlib.util.spec_from_file_location("workflow_module", graph_path)
+ module = importlib.util.module_from_spec(spec)
+ sys.modules["workflow_module"] = module
+ spec.loader.exec_module(module)
+ return module.Workflow
+
+
+async def aflow_inference(
+ dataset: DatasetType,
+ question: str,
+ entry_point: Optional[str] = None,
+ round: Optional[int] = None,
+ llm_name: str = "gpt-4o-mini",
+ optimized_path: str = "metagpt/ext/aflow/scripts/optimized",
+) -> Tuple[str, float]:
+ """AFLOW推理接口
+
+ Args:
+ dataset: 数据集名称
+ question: 输入问题
+ round: 指定使用的轮次,如果为None则使用最佳轮次
+ llm_name: 使用的LLM模型名称
+ optimized_path: 优化结果保存路径
+
+ Returns:
+ (答案, 成本)的元组
+ """
+ # 如果没有指定轮次,使用最佳轮次
+ if round is None:
+ round = load_best_round(dataset, optimized_path)
+
+ logger.info(f"Using round {round} for inference")
+
+ # 构建工作流路径并加载
+ graph_path = Path(optimized_path) / dataset / "workflows" / f"round_{round}" / "graph.py"
+ if not graph_path.exists():
+ raise FileNotFoundError(f"Workflow file not found: {graph_path}")
+
+ # 动态加载工作流类
+ WorkflowClass = load_workflow_class(str(graph_path))
+
+ # 创建工作流实例
+ llm_config = ModelsConfig.default().get(llm_name)
+ workflow = WorkflowClass(
+ name=f"{dataset}_workflow",
+ llm_config=llm_config,
+ dataset=dataset,
+ )
+
+ # 执行推理
+ if dataset in ["MBPP", "HumanEval"]:
+ # 代码类任务需要额外的entry_point参数
+ answer, cost = await workflow(question, entry_point=entry_point)
+ else:
+ answer, cost = await workflow(question)
+
+ return answer, cost
+
+
+if __name__ == "__main__":
+ asyncio.run(
+ aflow_inference(
+ dataset="MBPP",
+ question="write a function named add_two_numbers to calculate the sum of two numbers",
+ entry_point="add_two_numbers",
+ )
+ )
diff --git a/metagpt/ext/aflow/scripts/operator.py b/metagpt/ext/aflow/scripts/operator.py
new file mode 100644
index 000000000..903a962e0
--- /dev/null
+++ b/metagpt/ext/aflow/scripts/operator.py
@@ -0,0 +1,360 @@
+# -*- coding: utf-8 -*-
+# @Date : 6/27/2024 17:36 PM
+# @Author : didi
+# @Desc : operator demo of aflow
+import asyncio
+import concurrent.futures
+import random
+import sys
+import traceback
+from collections import Counter
+from typing import Dict, List, Tuple
+
+from tenacity import retry, stop_after_attempt, wait_fixed
+
+from metagpt.actions.action_node import ActionNode
+from metagpt.ext.aflow.scripts.operator_an import (
+ AnswerGenerateOp,
+ CodeGenerateOp,
+ FormatOp,
+ GenerateOp,
+ MdEnsembleOp,
+ ReflectionTestOp,
+ ReviewOp,
+ ReviseOp,
+ ScEnsembleOp,
+)
+from metagpt.ext.aflow.scripts.prompts.prompt import (
+ ANSWER_GENERATION_PROMPT,
+ FORMAT_PROMPT,
+ MD_ENSEMBLE_PROMPT,
+ PYTHON_CODE_VERIFIER_PROMPT,
+ REFLECTION_ON_PUBLIC_TEST_PROMPT,
+ REVIEW_PROMPT,
+ REVISE_PROMPT,
+ SC_ENSEMBLE_PROMPT,
+)
+from metagpt.ext.aflow.scripts.utils import (
+ extract_test_cases_from_jsonl,
+ test_case_2_test_function,
+)
+from metagpt.llm import LLM
+from metagpt.logs import logger
+
+
+class Operator:
+ def __init__(self, llm: LLM, name: str):
+ self.name = name
+ self.llm = llm
+
+ def __call__(self, *args, **kwargs):
+ raise NotImplementedError
+
+ async def _fill_node(self, op_class, prompt, mode=None, **extra_kwargs):
+ fill_kwargs = {"context": prompt, "llm": self.llm}
+ if mode:
+ fill_kwargs["mode"] = mode
+ fill_kwargs.update(extra_kwargs)
+ node = await ActionNode.from_pydantic(op_class).fill(**fill_kwargs)
+ return node.instruct_content.model_dump()
+
+
+class Custom(Operator):
+ def __init__(self, llm: LLM, name: str = "Custom"):
+ super().__init__(llm, name)
+
+ async def __call__(self, input, instruction):
+ prompt = instruction + input
+ response = await self._fill_node(GenerateOp, prompt, mode="single_fill")
+ return response
+
+
+class AnswerGenerate(Operator):
+ def __init__(self, llm: LLM, name: str = "AnswerGenerate"):
+ super().__init__(llm, name)
+
+ async def __call__(self, input: str, mode: str = None) -> Tuple[str, str]:
+ prompt = ANSWER_GENERATION_PROMPT.format(input=input)
+ response = await self._fill_node(AnswerGenerateOp, prompt, mode="xml_fill")
+ return response
+
+
+class CustomCodeGenerate(Operator):
+ def __init__(self, llm: LLM, name: str = "CustomCodeGenerate"):
+ super().__init__(llm, name)
+
+ async def __call__(self, problem, entry_point, instruction):
+ prompt = instruction + problem
+ response = await self._fill_node(GenerateOp, prompt, mode="code_fill", function_name=entry_point)
+ return response
+
+
+class ScEnsemble(Operator):
+ """
+ Paper: Self-Consistency Improves Chain of Thought Reasoning in Language Models
+ Link: https://arxiv.org/abs/2203.11171
+ Paper: Universal Self-Consistency for Large Language Model Generation
+ Link: https://arxiv.org/abs/2311.17311
+ """
+
+ def __init__(self, llm: LLM, name: str = "ScEnsemble"):
+ super().__init__(llm, name)
+
+ async def __call__(self, solutions: List[str], problem: str):
+ answer_mapping = {}
+ solution_text = ""
+ for index, solution in enumerate(solutions):
+ answer_mapping[chr(65 + index)] = index
+ solution_text += f"{chr(65 + index)}: \n{str(solution)}\n\n\n"
+
+ prompt = SC_ENSEMBLE_PROMPT.format(question=problem, solutions=solution_text)
+ response = await self._fill_node(ScEnsembleOp, prompt, mode="xml_fill")
+
+ answer = response.get("solution_letter", "")
+ answer = answer.strip().upper()
+
+ return {"response": solutions[answer_mapping[answer]]}
+
+
+def run_code(code):
+ try:
+ # Create a new global namespace
+ global_namespace = {}
+
+ disallowed_imports = [
+ "os",
+ "sys",
+ "subprocess",
+ "multiprocessing",
+ "matplotlib",
+ "seaborn",
+ "plotly",
+ "bokeh",
+ "ggplot",
+ "pylab",
+ "tkinter",
+ "PyQt5",
+ "wx",
+ "pyglet",
+ ]
+
+ # Check for prohibited imports
+ for lib in disallowed_imports:
+ if f"import {lib}" in code or f"from {lib}" in code:
+ logger.info("Detected prohibited import: %s", lib)
+ return "Error", f"Prohibited import: {lib} and graphing functionalities"
+
+ # Use exec to execute the code
+ exec(code, global_namespace)
+ # Assume the code defines a function named 'solve'
+ if "solve" in global_namespace and callable(global_namespace["solve"]):
+ result = global_namespace["solve"]()
+ return "Success", str(result)
+ else:
+ return "Error", "Function 'solve' not found"
+ except Exception as e:
+ exc_type, exc_value, exc_traceback = sys.exc_info()
+ tb_str = traceback.format_exception(exc_type, exc_value, exc_traceback)
+ return "Error", f"Execution error: {str(e)}\n{''.join(tb_str)}"
+
+
+class Programmer(Operator):
+ def __init__(self, llm: LLM, name: str = "Programmer"):
+ super().__init__(llm, name)
+
+ async def exec_code(self, code, timeout=30):
+ """
+ Asynchronously execute code and return an error if timeout occurs.
+ """
+ loop = asyncio.get_running_loop()
+ with concurrent.futures.ProcessPoolExecutor(max_workers=1) as executor:
+ try:
+ # Submit run_code task to the process pool
+ future = loop.run_in_executor(executor, run_code, code)
+ # Wait for the task to complete or timeout
+ result = await asyncio.wait_for(future, timeout=timeout)
+ return result
+ except asyncio.TimeoutError:
+ # Timeout, attempt to shut down the process pool
+ executor.shutdown(wait=False, cancel_futures=True)
+ return "Error", "Code execution timed out"
+ except Exception as e:
+ return "Error", f"Unknown error: {str(e)}"
+
+ async def code_generate(self, problem, analysis, feedback, mode):
+ """
+ Asynchronous method to generate code.
+ """
+ prompt = PYTHON_CODE_VERIFIER_PROMPT.format(problem=problem, analysis=analysis, feedback=feedback)
+ response = await self._fill_node(CodeGenerateOp, prompt, mode, function_name="solve")
+ return response
+
+ @retry(stop=stop_after_attempt(3), wait=wait_fixed(2))
+ async def __call__(self, problem: str, analysis: str = "None"):
+ """
+ Call method, generate code and execute, retry up to 3 times.
+ """
+ code = None
+ output = None
+ feedback = ""
+ for i in range(3):
+ code_response = await self.code_generate(problem, analysis, feedback, mode="code_fill")
+ code = code_response.get("code")
+ if not code:
+ return {"code": code, "output": "No code generated"}
+ status, output = await self.exec_code(code)
+ if status == "Success":
+ return {"code": code, "output": output}
+ else:
+ logger.info(f"Execution error on attempt {i + 1}, error message: {output}")
+ feedback = (
+ f"\nThe result of the error from the code you wrote in the previous round:\n"
+ f"Code: {code}\n\nStatus: {status}, {output}"
+ )
+ return {"code": code, "output": output}
+
+
+class Test(Operator):
+ def __init__(self, llm: LLM, name: str = "Test"):
+ super().__init__(llm, name)
+
+ def exec_code(self, solution, entry_point):
+ test_cases = extract_test_cases_from_jsonl(entry_point)
+
+ fail_cases = []
+ for test_case in test_cases:
+ test_code = test_case_2_test_function(solution, test_case, entry_point)
+ try:
+ exec(test_code, globals())
+ except AssertionError as e:
+ exc_type, exc_value, exc_traceback = sys.exc_info()
+ tb_str = traceback.format_exception(exc_type, exc_value, exc_traceback)
+ with open("tester.txt", "a") as f:
+ f.write("test_error of " + entry_point + "\n")
+ error_infomation = {
+ "test_fail_case": {
+ "test_case": test_case,
+ "error_type": "AssertionError",
+ "error_message": str(e),
+ "traceback": tb_str,
+ }
+ }
+ fail_cases.append(error_infomation)
+ except Exception as e:
+ with open("tester.txt", "a") as f:
+ f.write(entry_point + " " + str(e) + "\n")
+ return {"exec_fail_case": str(e)}
+ if fail_cases != []:
+ return fail_cases
+ else:
+ return "no error"
+
+ async def __call__(self, problem, solution, entry_point, test_loop: int = 3):
+ """
+ "Test": {
+ "description": "Test the solution with test cases, if the solution is correct, return 'no error', if the solution is incorrect, return reflect on the soluion and the error information",
+ "interface": "test(problem: str, solution: str, entry_point: str) -> str"
+ }
+ """
+ for _ in range(test_loop):
+ result = self.exec_code(solution, entry_point)
+ if result == "no error":
+ return {"result": True, "solution": solution}
+ elif "exec_fail_case" in result:
+ result = result["exec_fail_case"]
+ prompt = REFLECTION_ON_PUBLIC_TEST_PROMPT.format(
+ problem=problem,
+ solution=solution,
+ exec_pass=f"executed unsuccessfully, error: \n {result}",
+ test_fail="executed unsucessfully",
+ )
+ response = await self._fill_node(ReflectionTestOp, prompt, mode="code_fill")
+ solution = response["reflection_and_solution"]
+ else:
+ prompt = REFLECTION_ON_PUBLIC_TEST_PROMPT.format(
+ problem=problem,
+ solution=solution,
+ exec_pass="executed successfully",
+ test_fail=result,
+ )
+ response = await self._fill_node(ReflectionTestOp, prompt, mode="code_fill")
+ solution = response["reflection_and_solution"]
+
+ result = self.exec_code(solution, entry_point)
+ if result == "no error":
+ return {"result": True, "solution": solution}
+ else:
+ return {"result": False, "solution": solution}
+
+
+class Format(Operator):
+ def __init__(self, llm: LLM, name: str = "Format"):
+ super().__init__(llm, name)
+
+ async def __call__(self, problem, solution, mode: str = None):
+ prompt = FORMAT_PROMPT.format(problem_description=problem, solution=solution)
+ response = await self._fill_node(FormatOp, prompt, mode)
+ return response
+
+
+class Review(Operator):
+ def __init__(self, llm: LLM, name: str = "Review"):
+ super().__init__(llm, name)
+
+ async def __call__(self, problem, solution, mode: str = None):
+ prompt = REVIEW_PROMPT.format(problem=problem, solution=solution)
+ response = await self._fill_node(ReviewOp, prompt, mode="xml_fill")
+ return response
+
+
+class Revise(Operator):
+ def __init__(self, llm: LLM, name: str = "Revise"):
+ super().__init__(llm, name)
+
+ async def __call__(self, problem, solution, feedback, mode: str = None):
+ prompt = REVISE_PROMPT.format(problem=problem, solution=solution, feedback=feedback)
+ response = await self._fill_node(ReviseOp, prompt, mode="xml_fill")
+ return response
+
+
+class MdEnsemble(Operator):
+ """
+ Paper: Can Generalist Foundation Models Outcompete Special-Purpose Tuning? Case Study in Medicine
+ Link: https://arxiv.org/abs/2311.16452
+ """
+
+ def __init__(self, llm: LLM, name: str = "MdEnsemble", vote_count: int = 5):
+ super().__init__(llm, name)
+ self.vote_count = vote_count
+
+ @staticmethod
+ def shuffle_answers(solutions: List[str]) -> Tuple[List[str], Dict[str, str]]:
+ shuffled_solutions = solutions.copy()
+ random.shuffle(shuffled_solutions)
+ answer_mapping = {chr(65 + i): solutions.index(solution) for i, solution in enumerate(shuffled_solutions)}
+ return shuffled_solutions, answer_mapping
+
+ async def __call__(self, solutions: List[str], problem: str, mode: str = None):
+ logger.info(f"solution count: {len(solutions)}")
+ all_responses = []
+
+ for _ in range(self.vote_count):
+ shuffled_solutions, answer_mapping = self.shuffle_answers(solutions)
+
+ solution_text = ""
+ for index, solution in enumerate(shuffled_solutions):
+ solution_text += f"{chr(65 + index)}: \n{str(solution)}\n\n\n"
+
+ prompt = MD_ENSEMBLE_PROMPT.format(solutions=solution_text, question=problem)
+ response = await self._fill_node(MdEnsembleOp, prompt, mode="xml_fill")
+
+ answer = response.get("solution_letter", "A")
+ answer = answer.strip().upper()
+
+ if answer in answer_mapping:
+ original_index = answer_mapping[answer]
+ all_responses.append(original_index)
+
+ most_frequent_index = Counter(all_responses).most_common(1)[0][0]
+ final_answer = solutions[most_frequent_index]
+ return {"solution": final_answer}
diff --git a/metagpt/ext/aflow/scripts/operator_an.py b/metagpt/ext/aflow/scripts/operator_an.py
new file mode 100644
index 000000000..d0201dea2
--- /dev/null
+++ b/metagpt/ext/aflow/scripts/operator_an.py
@@ -0,0 +1,54 @@
+# -*- coding: utf-8 -*-
+# @Date : 6/27/2024 19:46 PM
+# @Author : didi
+# @Desc : action nodes for operator
+
+from pydantic import BaseModel, Field
+
+
+class GenerateOp(BaseModel):
+ response: str = Field(default="", description="Your solution for this problem")
+
+
+class CodeGenerateOp(BaseModel):
+ code: str = Field(default="", description="Your complete code solution for this problem")
+
+
+class AnswerGenerateOp(BaseModel):
+ thought: str = Field(default="", description="The step by step thinking process")
+ answer: str = Field(default="", description="The final answer to the question")
+
+
+class FormatOp(BaseModel):
+ solution: str = Field(default="", description="Your formatted answer for this problem")
+
+
+class ScEnsembleOp(BaseModel):
+ thought: str = Field(default="", description="The thought of the most consistent solution.")
+ solution_letter: str = Field(default="", description="The letter of most consistent solution.")
+
+
+class ReflectionTestOp(BaseModel):
+ reflection_and_solution: str = Field(
+ default="", description="Corrective solution for code execution errors or test case failures"
+ )
+
+
+class MdEnsembleOp(BaseModel):
+ thought: str = Field(default="", description="Step-by-step analysis of the solutions to determine the best one.")
+ solution_letter: str = Field(default="", description="The letter of the chosen best solution (only one letter).")
+
+
+class ReviewOp(BaseModel):
+ review_result: bool = Field(
+ default=False,
+ description="The Review Result (Bool). If you think this solution looks good for you, return 'true'; If not, return 'false'",
+ )
+ feedback: str = Field(
+ default="",
+ description="Your FeedBack for this problem based on the criteria. If the review result is true, you can put it 'nothing here'.",
+ )
+
+
+class ReviseOp(BaseModel):
+ solution: str = Field(default="", description="Based on the feedback, revised solution for this problem")
diff --git a/metagpt/ext/aflow/scripts/optimized/__init__.py b/metagpt/ext/aflow/scripts/optimized/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/metagpt/ext/aflow/scripts/optimizer.py b/metagpt/ext/aflow/scripts/optimizer.py
new file mode 100644
index 000000000..0ac4827e7
--- /dev/null
+++ b/metagpt/ext/aflow/scripts/optimizer.py
@@ -0,0 +1,199 @@
+# -*- coding: utf-8 -*-
+# @Date : 8/12/2024 22:00 PM
+# @Author : issac
+# @Desc : optimizer for graph
+
+import asyncio
+import time
+from typing import List, Literal
+
+from pydantic import BaseModel, Field
+
+from metagpt.actions.action_node import ActionNode
+from metagpt.ext.aflow.scripts.evaluator import DatasetType
+from metagpt.ext.aflow.scripts.optimizer_utils.convergence_utils import ConvergenceUtils
+from metagpt.ext.aflow.scripts.optimizer_utils.data_utils import DataUtils
+from metagpt.ext.aflow.scripts.optimizer_utils.evaluation_utils import EvaluationUtils
+from metagpt.ext.aflow.scripts.optimizer_utils.experience_utils import ExperienceUtils
+from metagpt.ext.aflow.scripts.optimizer_utils.graph_utils import GraphUtils
+from metagpt.logs import logger
+from metagpt.provider.llm_provider_registry import create_llm_instance
+
+QuestionType = Literal["math", "code", "qa"]
+OptimizerType = Literal["Graph", "Test"]
+
+
+class GraphOptimize(BaseModel):
+ modification: str = Field(default="", description="modification")
+ graph: str = Field(default="", description="graph")
+ prompt: str = Field(default="", description="prompt")
+
+
+class Optimizer:
+ def __init__(
+ self,
+ dataset: DatasetType,
+ question_type: QuestionType,
+ opt_llm_config,
+ exec_llm_config,
+ operators: List,
+ sample: int,
+ check_convergence: bool = False,
+ optimized_path: str = None,
+ initial_round: int = 1,
+ max_rounds: int = 20,
+ validation_rounds: int = 5,
+ ) -> None:
+ self.optimize_llm_config = opt_llm_config
+ self.optimize_llm = create_llm_instance(self.optimize_llm_config)
+ self.execute_llm_config = exec_llm_config
+
+ self.dataset = dataset
+ self.type = question_type
+ self.check_convergence = check_convergence
+
+ self.graph = None
+ self.operators = operators
+
+ self.root_path = f"{optimized_path}/{self.dataset}"
+ self.sample = sample
+ self.top_scores = []
+ self.round = initial_round
+ self.max_rounds = max_rounds
+ self.validation_rounds = validation_rounds
+
+ self.graph_utils = GraphUtils(self.root_path)
+ self.data_utils = DataUtils(self.root_path)
+ self.experience_utils = ExperienceUtils(self.root_path)
+ self.evaluation_utils = EvaluationUtils(self.root_path)
+ self.convergence_utils = ConvergenceUtils(self.root_path)
+
+ def optimize(self, mode: OptimizerType = "Graph"):
+ if mode == "Test":
+ test_n = 3 # validation datasets's execution number
+ for i in range(test_n):
+ loop = asyncio.new_event_loop()
+ asyncio.set_event_loop(loop)
+ score = loop.run_until_complete(self.test())
+ return None
+
+ for opt_round in range(self.max_rounds):
+ loop = asyncio.new_event_loop()
+ asyncio.set_event_loop(loop)
+
+ retry_count = 0
+ max_retries = 1
+
+ while retry_count < max_retries:
+ try:
+ score = loop.run_until_complete(self._optimize_graph())
+ break
+ except Exception as e:
+ retry_count += 1
+ logger.info(f"Error occurred: {e}. Retrying... (Attempt {retry_count}/{max_retries})")
+ if retry_count == max_retries:
+ logger.info("Max retries reached. Moving to next round.")
+ score = None
+
+ wait_time = 5 * retry_count
+ time.sleep(wait_time)
+
+ if retry_count < max_retries:
+ loop = asyncio.new_event_loop()
+ asyncio.set_event_loop(loop)
+
+ self.round += 1
+ logger.info(f"Score for round {self.round}: {score}")
+
+ converged, convergence_round, final_round = self.convergence_utils.check_convergence(top_k=3)
+
+ if converged and self.check_convergence:
+ logger.info(
+ f"Convergence detected, occurred in round {convergence_round}, final round is {final_round}"
+ )
+ # Print average scores and standard deviations for each round
+ self.convergence_utils.print_results()
+ break
+
+ time.sleep(5)
+
+ async def _optimize_graph(self):
+ validation_n = self.validation_rounds # validation datasets's execution number
+ graph_path = f"{self.root_path}/workflows"
+ data = self.data_utils.load_results(graph_path)
+
+ if self.round == 1:
+ directory = self.graph_utils.create_round_directory(graph_path, self.round)
+ # Load graph using graph_utils
+ self.graph = self.graph_utils.load_graph(self.round, graph_path)
+ avg_score = await self.evaluation_utils.evaluate_graph(self, directory, validation_n, data, initial=True)
+
+ # Create a loop until the generated graph meets the check conditions
+ while True:
+ directory = self.graph_utils.create_round_directory(graph_path, self.round + 1)
+
+ top_rounds = self.data_utils.get_top_rounds(self.sample)
+ sample = self.data_utils.select_round(top_rounds)
+
+ prompt, graph_load = self.graph_utils.read_graph_files(sample["round"], graph_path)
+ graph = self.graph_utils.extract_solve_graph(graph_load)
+
+ processed_experience = self.experience_utils.load_experience()
+ experience = self.experience_utils.format_experience(processed_experience, sample["round"])
+
+ operator_description = self.graph_utils.load_operators_description(self.operators)
+ log_data = self.data_utils.load_log(sample["round"])
+
+ graph_optimize_prompt = self.graph_utils.create_graph_optimize_prompt(
+ experience, sample["score"], graph[0], prompt, operator_description, self.type, log_data
+ )
+
+ graph_optimize_node = await ActionNode.from_pydantic(GraphOptimize).fill(
+ context=graph_optimize_prompt, mode="xml_fill", llm=self.optimize_llm
+ )
+
+ response = await self.graph_utils.get_graph_optimize_response(graph_optimize_node)
+
+ # Check if the modification meets the conditions
+ check = self.experience_utils.check_modification(
+ processed_experience, response["modification"], sample["round"]
+ )
+
+ # If `check` is True, break the loop; otherwise, regenerate the graph
+ if check:
+ break
+
+ # Save the graph and evaluate
+ self.graph_utils.write_graph_files(directory, response, self.round + 1, self.dataset)
+
+ experience = self.experience_utils.create_experience_data(sample, response["modification"])
+
+ self.graph = self.graph_utils.load_graph(self.round + 1, graph_path)
+
+ logger.info(directory)
+
+ avg_score = await self.evaluation_utils.evaluate_graph(self, directory, validation_n, data, initial=False)
+
+ self.experience_utils.update_experience(directory, experience, avg_score)
+
+ return avg_score
+
+ async def test(self):
+ rounds = [5] # You can choose the rounds you want to test here.
+ data = []
+
+ graph_path = f"{self.root_path}/workflows_test"
+ json_file_path = self.data_utils.get_results_file_path(graph_path)
+
+ data = self.data_utils.load_results(graph_path)
+
+ for round in rounds:
+ directory = self.graph_utils.create_round_directory(graph_path, round)
+ self.graph = self.graph_utils.load_graph(round, graph_path)
+
+ score, avg_cost, total_cost = await self.evaluation_utils.evaluate_graph_test(self, directory, is_test=True)
+
+ new_data = self.data_utils.create_result_data(round, score, avg_cost, total_cost)
+ data.append(new_data)
+
+ self.data_utils.save_results(json_file_path, data)
diff --git a/metagpt/ext/aflow/scripts/optimizer_utils/convergence_utils.py b/metagpt/ext/aflow/scripts/optimizer_utils/convergence_utils.py
new file mode 100644
index 000000000..0e275f496
--- /dev/null
+++ b/metagpt/ext/aflow/scripts/optimizer_utils/convergence_utils.py
@@ -0,0 +1,135 @@
+# -*- coding: utf-8 -*-
+# @Date : 9/23/2024 10:00 AM
+# @Author : Issac
+# @Desc :
+
+import json
+import os
+
+import numpy as np
+
+from metagpt.logs import logger
+
+
+class ConvergenceUtils:
+ def __init__(self, root_path):
+ self.root_path = root_path
+ self.data = None
+ self.rounds = None
+ self.avg_scores, self.stds = None, None
+
+ def load_data(self, root_path):
+ """
+ Read JSON file, create a new file if it doesn't exist, then return the data.
+ """
+ rounds_dir = os.path.join(root_path, "workflows")
+ result_file = os.path.join(rounds_dir, "results.json")
+
+ # Ensure directory exists
+ os.makedirs(rounds_dir, exist_ok=True)
+
+ # If file doesn't exist, create a new one with an empty list
+ if not os.path.exists(result_file):
+ with open(result_file, "w") as file:
+ json.dump([], file)
+
+ # Read file and return data
+ with open(result_file, "r") as file:
+ return json.load(file)
+
+ def process_rounds(self):
+ """
+ Organize data by round, return a dictionary of scores by round.
+ """
+ self.data = self.load_data(root_path=self.root_path)
+ rounds = {}
+ for entry in self.data:
+ round_number = entry["round"]
+ score = entry["score"]
+ if round_number not in rounds:
+ rounds[round_number] = []
+ rounds[round_number].append(score)
+ return rounds
+
+ def calculate_avg_and_std(self):
+ """
+ Calculate average score and standard deviation for each round, return two lists: average scores and standard deviations.
+ """
+ self.rounds = self.process_rounds()
+
+ sorted_rounds = sorted(self.rounds.items(), key=lambda x: x[0])
+ avg_scores = []
+ stds = []
+ for round_number, scores in sorted_rounds:
+ avg_scores.append(np.mean(scores))
+ stds.append(np.std(scores))
+ return avg_scores, stds
+
+ def check_convergence(self, top_k=3, z=0, consecutive_rounds=5):
+ """
+ Check for convergence. z is the z-score corresponding to the confidence level.
+ consecutive_rounds is the number of consecutive rounds that must meet the stop condition.
+ """
+ # Calculate average score and standard deviation for each round
+ self.avg_scores, self.stds = self.calculate_avg_and_std()
+ # If total rounds are not enough to calculate top_k+1 rounds, return not converged
+ if len(self.avg_scores) < top_k + 1:
+ return False, None, None
+ convergence_count = 0 # Convergence counter
+ previous_y = None # Y value of the previous round (average of top_k scores)
+ sigma_y_previous = None # Standard error of Y value from previous round
+ for i in range(len(self.avg_scores)):
+ # Dynamically select top_k from current round and all previous rounds
+ top_k_indices = np.argsort(self.avg_scores[: i + 1])[::-1][
+ :top_k
+ ] # Select top k indices by descending average score
+ top_k_scores = [self.avg_scores[j] for j in top_k_indices] # Get list of top k scores
+ top_k_stds = [
+ self.stds[j] for j in top_k_indices
+ ] # Get list of standard deviations corresponding to top k scores
+ # Calculate mean of top k scores for current round, i.e., y_current
+ y_current = np.mean(top_k_scores)
+ # Calculate standard error of y_current (sigma_y_current), representing score dispersion
+ sigma_y_current = np.sqrt(np.sum([s**2 for s in top_k_stds]) / (top_k**2))
+ # If not the first round, calculate change in Y (Delta_Y) and corresponding standard error
+ if previous_y is not None:
+ # Calculate Y difference between current round and previous round
+ delta_y = y_current - previous_y
+ # Calculate standard error of Y difference (sigma_Delta_Y)
+ sigma_delta_y = np.sqrt(sigma_y_current**2 + sigma_y_previous**2)
+ # Check if Y change is within acceptable confidence interval, i.e., convergence condition
+ if abs(delta_y) <= z * sigma_delta_y:
+ convergence_count += 1
+ # If consecutive converged rounds reach set value, return convergence information
+ if convergence_count >= consecutive_rounds:
+ return True, i - consecutive_rounds + 1, i
+ else:
+ # If change is large, reset convergence counter
+ convergence_count = 0
+ # Update Y value and standard error for previous round
+ previous_y = y_current
+ sigma_y_previous = sigma_y_current
+ # If convergence condition not met, return not converged
+ return False, None, None
+
+ def print_results(self):
+ """
+ Print average score and standard deviation for all rounds.
+ """
+ self.avg_scores, self.stds = self.calculate_avg_and_std()
+ for i, (avg_score, std) in enumerate(zip(self.avg_scores, self.stds), 1):
+ logger.info(f"Round {i}: Average Score = {avg_score:.4f}, Standard Deviation = {std:.4f}")
+
+
+if __name__ == "__main__":
+ # Use this class and specify top_k
+ checker = ConvergenceUtils("path") # For example, set top_k=5
+ converged, convergence_round, final_round = checker.check_convergence()
+
+ if converged:
+ logger.info(f"Convergence detected, occurred at round {convergence_round}, final round is {final_round}")
+ else:
+ logger.info("No convergence detected within all rounds")
+
+ # Print average score and standard deviation for each round
+ checker.print_results()
diff --git a/metagpt/ext/aflow/scripts/optimizer_utils/data_utils.py b/metagpt/ext/aflow/scripts/optimizer_utils/data_utils.py
new file mode 100644
index 000000000..2a09e0820
--- /dev/null
+++ b/metagpt/ext/aflow/scripts/optimizer_utils/data_utils.py
@@ -0,0 +1,149 @@
+import datetime
+import json
+import os
+import random
+
+import numpy as np
+import pandas as pd
+
+from metagpt.logs import logger
+from metagpt.utils.common import read_json_file, write_json_file
+
+
+class DataUtils:
+ def __init__(self, root_path: str):
+ self.root_path = root_path
+ self.top_scores = []
+
+ def load_results(self, path: str) -> list:
+ result_path = os.path.join(path, "results.json")
+ if os.path.exists(result_path):
+ with open(result_path, "r") as json_file:
+ try:
+ return json.load(json_file)
+ except json.JSONDecodeError:
+ return []
+ return []
+
+ def get_top_rounds(self, sample: int, path=None, mode="Graph"):
+ self._load_scores(path, mode)
+ unique_rounds = set()
+ unique_top_scores = []
+
+ first_round = next((item for item in self.top_scores if item["round"] == 1), None)
+ if first_round:
+ unique_top_scores.append(first_round)
+ unique_rounds.add(1)
+
+ for item in self.top_scores:
+ if item["round"] not in unique_rounds:
+ unique_top_scores.append(item)
+ unique_rounds.add(item["round"])
+
+ if len(unique_top_scores) >= sample:
+ break
+
+ return unique_top_scores
+
+ def select_round(self, items):
+ if not items:
+ raise ValueError("Item list is empty.")
+
+ sorted_items = sorted(items, key=lambda x: x["score"], reverse=True)
+ scores = [item["score"] * 100 for item in sorted_items]
+
+ probabilities = self._compute_probabilities(scores)
+ logger.info(f"\nMixed probability distribution: {probabilities}")
+ logger.info(f"\nSorted rounds: {sorted_items}")
+
+ selected_index = np.random.choice(len(sorted_items), p=probabilities)
+ logger.info(f"\nSelected index: {selected_index}, Selected item: {sorted_items[selected_index]}")
+
+ return sorted_items[selected_index]
+
+ def _compute_probabilities(self, scores, alpha=0.2, lambda_=0.3):
+ scores = np.array(scores, dtype=np.float64)
+ n = len(scores)
+
+ if n == 0:
+ raise ValueError("Score list is empty.")
+
+ uniform_prob = np.full(n, 1.0 / n, dtype=np.float64)
+
+ max_score = np.max(scores)
+ shifted_scores = scores - max_score
+ exp_weights = np.exp(alpha * shifted_scores)
+
+ sum_exp_weights = np.sum(exp_weights)
+ if sum_exp_weights == 0:
+ raise ValueError("Sum of exponential weights is 0, cannot normalize.")
+
+ score_prob = exp_weights / sum_exp_weights
+
+ mixed_prob = lambda_ * uniform_prob + (1 - lambda_) * score_prob
+
+ total_prob = np.sum(mixed_prob)
+ if not np.isclose(total_prob, 1.0):
+ mixed_prob = mixed_prob / total_prob
+
+ return mixed_prob
+
+ def load_log(self, cur_round, path=None, mode: str = "Graph"):
+ if mode == "Graph":
+ log_dir = os.path.join(self.root_path, "workflows", f"round_{cur_round}", "log.json")
+ else:
+ log_dir = path
+
+ # 检查文件是否存在
+ if not os.path.exists(log_dir):
+ return "" # 如果文件不存在,返回空字符串
+ logger.info(log_dir)
+ data = read_json_file(log_dir, encoding="utf-8")
+
+ if isinstance(data, dict):
+ data = [data]
+ elif not isinstance(data, list):
+ data = list(data)
+
+ if not data:
+ return ""
+
+ sample_size = min(3, len(data))
+ random_samples = random.sample(data, sample_size)
+
+ log = ""
+ for sample in random_samples:
+ log += json.dumps(sample, indent=4, ensure_ascii=False) + "\n\n"
+
+ return log
+
+ def get_results_file_path(self, graph_path: str) -> str:
+ return os.path.join(graph_path, "results.json")
+
+ def create_result_data(self, round: int, score: float, avg_cost: float, total_cost: float) -> dict:
+ now = datetime.datetime.now()
+ return {"round": round, "score": score, "avg_cost": avg_cost, "total_cost": total_cost, "time": now}
+
+ def save_results(self, json_file_path: str, data: list):
+ write_json_file(json_file_path, data, encoding="utf-8", indent=4)
+
+ def _load_scores(self, path=None, mode="Graph"):
+ if mode == "Graph":
+ rounds_dir = os.path.join(self.root_path, "workflows")
+ else:
+ rounds_dir = path
+
+ result_file = os.path.join(rounds_dir, "results.json")
+ self.top_scores = []
+
+ data = read_json_file(result_file, encoding="utf-8")
+ df = pd.DataFrame(data)
+
+ scores_per_round = df.groupby("round")["score"].mean().to_dict()
+
+ for round_number, average_score in scores_per_round.items():
+ self.top_scores.append({"round": round_number, "score": average_score})
+
+ self.top_scores.sort(key=lambda x: x["score"], reverse=True)
+
+ return self.top_scores
diff --git a/metagpt/ext/aflow/scripts/optimizer_utils/evaluation_utils.py b/metagpt/ext/aflow/scripts/optimizer_utils/evaluation_utils.py
new file mode 100644
index 000000000..77683017e
--- /dev/null
+++ b/metagpt/ext/aflow/scripts/optimizer_utils/evaluation_utils.py
@@ -0,0 +1,63 @@
+from metagpt.ext.aflow.scripts.evaluator import Evaluator
+
+
+class EvaluationUtils:
+ def __init__(self, root_path: str):
+ self.root_path = root_path
+
+ async def evaluate_initial_round(self, optimizer, graph_path, directory, validation_n, data):
+ # 使用 optimizer 的 graph_utils 来加载图
+ optimizer.graph = optimizer.graph_utils.load_graph(optimizer.round, graph_path)
+ evaluator = Evaluator(eval_path=directory)
+
+ for i in range(validation_n):
+ score, avg_cost, total_cost = await evaluator.graph_evaluate(
+ optimizer.dataset,
+ optimizer.graph,
+ {"dataset": optimizer.dataset, "llm_config": optimizer.execute_llm_config},
+ directory,
+ is_test=False,
+ )
+
+ new_data = optimizer.data_utils.create_result_data(optimizer.round, score, avg_cost, total_cost)
+ data.append(new_data)
+
+ result_path = optimizer.data_utils.get_results_file_path(graph_path)
+ optimizer.data_utils.save_results(result_path, data)
+
+ return data
+
+ async def evaluate_graph(self, optimizer, directory, validation_n, data, initial=False):
+ evaluator = Evaluator(eval_path=directory)
+ sum_score = 0
+
+ for i in range(validation_n):
+ score, avg_cost, total_cost = await evaluator.graph_evaluate(
+ optimizer.dataset,
+ optimizer.graph,
+ {"dataset": optimizer.dataset, "llm_config": optimizer.execute_llm_config},
+ directory,
+ is_test=False,
+ )
+
+ cur_round = optimizer.round + 1 if initial is False else optimizer.round
+
+ new_data = optimizer.data_utils.create_result_data(cur_round, score, avg_cost, total_cost)
+ data.append(new_data)
+
+ result_path = optimizer.data_utils.get_results_file_path(f"{optimizer.root_path}/workflows")
+ optimizer.data_utils.save_results(result_path, data)
+
+ sum_score += score
+
+ return sum_score / validation_n
+
+ async def evaluate_graph_test(self, optimizer, directory, is_test=True):
+ evaluator = Evaluator(eval_path=directory)
+ return await evaluator.graph_evaluate(
+ optimizer.dataset,
+ optimizer.graph,
+ {"dataset": optimizer.dataset, "llm_config": optimizer.execute_llm_config},
+ directory,
+ is_test=is_test,
+ )
diff --git a/metagpt/ext/aflow/scripts/optimizer_utils/experience_utils.py b/metagpt/ext/aflow/scripts/optimizer_utils/experience_utils.py
new file mode 100644
index 000000000..43f9eb1d5
--- /dev/null
+++ b/metagpt/ext/aflow/scripts/optimizer_utils/experience_utils.py
@@ -0,0 +1,96 @@
+import json
+import os
+from collections import defaultdict
+
+from metagpt.logs import logger
+from metagpt.utils.common import read_json_file, write_json_file
+
+
+class ExperienceUtils:
+ def __init__(self, root_path: str):
+ self.root_path = root_path
+
+ def load_experience(self, path=None, mode: str = "Graph"):
+ if mode == "Graph":
+ rounds_dir = os.path.join(self.root_path, "workflows")
+ else:
+ rounds_dir = path
+
+ experience_data = defaultdict(lambda: {"score": None, "success": {}, "failure": {}})
+
+ for round_dir in os.listdir(rounds_dir):
+ if os.path.isdir(os.path.join(rounds_dir, round_dir)) and round_dir.startswith("round_"):
+ round_path = os.path.join(rounds_dir, round_dir)
+ try:
+ round_number = int(round_dir.split("_")[1])
+ json_file_path = os.path.join(round_path, "experience.json")
+ if os.path.exists(json_file_path):
+ data = read_json_file(json_file_path, encoding="utf-8")
+ father_node = data["father node"]
+
+ if experience_data[father_node]["score"] is None:
+ experience_data[father_node]["score"] = data["before"]
+
+ if data["succeed"]:
+ experience_data[father_node]["success"][round_number] = {
+ "modification": data["modification"],
+ "score": data["after"],
+ }
+ else:
+ experience_data[father_node]["failure"][round_number] = {
+ "modification": data["modification"],
+ "score": data["after"],
+ }
+ except Exception as e:
+ logger.info(f"Error processing {round_dir}: {str(e)}")
+
+ experience_data = dict(experience_data)
+
+ output_path = os.path.join(rounds_dir, "processed_experience.json")
+ with open(output_path, "w", encoding="utf-8") as outfile:
+ json.dump(experience_data, outfile, indent=4, ensure_ascii=False)
+
+ logger.info(f"Processed experience data saved to {output_path}")
+ return experience_data
+
+ def format_experience(self, processed_experience, sample_round):
+ experience_data = processed_experience.get(sample_round)
+ if experience_data:
+ experience = f"Original Score: {experience_data['score']}\n"
+ experience += "These are some conclusions drawn from experience:\n\n"
+ for key, value in experience_data["failure"].items():
+ experience += f"-Absolutely prohibit {value['modification']} (Score: {value['score']})\n"
+ for key, value in experience_data["success"].items():
+ experience += f"-Absolutely prohibit {value['modification']} \n"
+ experience += "\n\nNote: Take into account past failures and avoid repeating the same mistakes, as these failures indicate that these approaches are ineffective. You must fundamentally change your way of thinking, rather than simply using more advanced Python syntax like for, if, else, etc., or modifying the prompt."
+ else:
+ experience = f"No experience data found for round {sample_round}."
+ return experience
+
+ def check_modification(self, processed_experience, modification, sample_round):
+ experience_data = processed_experience.get(sample_round)
+ if experience_data:
+ for key, value in experience_data["failure"].items():
+ if value["modification"] == modification:
+ return False
+ for key, value in experience_data["success"].items():
+ if value["modification"] == modification:
+ return False
+ return True
+ else:
+ return True # 如果 experience_data 为空,也返回 True
+
+ def create_experience_data(self, sample, modification):
+ return {
+ "father node": sample["round"],
+ "modification": modification,
+ "before": sample["score"],
+ "after": None,
+ "succeed": None,
+ }
+
+ def update_experience(self, directory, experience, avg_score):
+ experience["after"] = avg_score
+ experience["succeed"] = bool(avg_score > experience["before"])
+
+ write_json_file(os.path.join(directory, "experience.json"), experience, encoding="utf-8", indent=4)
diff --git a/metagpt/ext/aflow/scripts/optimizer_utils/graph_utils.py b/metagpt/ext/aflow/scripts/optimizer_utils/graph_utils.py
new file mode 100644
index 000000000..a0ebe9b26
--- /dev/null
+++ b/metagpt/ext/aflow/scripts/optimizer_utils/graph_utils.py
@@ -0,0 +1,125 @@
+import json
+import os
+import re
+import time
+import traceback
+from typing import List
+
+from metagpt.ext.aflow.scripts.prompts.optimize_prompt import (
+ WORKFLOW_CUSTOM_USE,
+ WORKFLOW_INPUT,
+ WORKFLOW_OPTIMIZE_PROMPT,
+ WORKFLOW_TEMPLATE,
+)
+from metagpt.logs import logger
+
+
+class GraphUtils:
+ def __init__(self, root_path: str):
+ self.root_path = root_path
+
+ def create_round_directory(self, graph_path: str, round_number: int) -> str:
+ directory = os.path.join(graph_path, f"round_{round_number}")
+ os.makedirs(directory, exist_ok=True)
+ return directory
+
+ def load_graph(self, round_number: int, workflows_path: str):
+ workflows_path = workflows_path.replace("\\", ".").replace("/", ".")
+ graph_module_name = f"{workflows_path}.round_{round_number}.graph"
+
+ try:
+ graph_module = __import__(graph_module_name, fromlist=[""])
+ graph_class = getattr(graph_module, "Workflow")
+ return graph_class
+ except ImportError as e:
+ logger.info(f"Error loading graph for round {round_number}: {e}")
+ raise
+
+ def read_graph_files(self, round_number: int, workflows_path: str):
+ prompt_file_path = os.path.join(workflows_path, f"round_{round_number}", "prompt.py")
+ graph_file_path = os.path.join(workflows_path, f"round_{round_number}", "graph.py")
+
+ try:
+ with open(prompt_file_path, "r", encoding="utf-8") as file:
+ prompt_content = file.read()
+ with open(graph_file_path, "r", encoding="utf-8") as file:
+ graph_content = file.read()
+ except FileNotFoundError as e:
+ logger.info(f"Error: File not found for round {round_number}: {e}")
+ raise
+ except Exception as e:
+ logger.info(f"Error loading prompt for round {round_number}: {e}")
+ raise
+ return prompt_content, graph_content
+
+ def extract_solve_graph(self, graph_load: str) -> List[str]:
+ pattern = r"class Workflow:.+"
+ return re.findall(pattern, graph_load, re.DOTALL)
+
+ def load_operators_description(self, operators: List[str]) -> str:
+ path = f"{self.root_path}/workflows/template/operator.json"
+ operators_description = ""
+ for id, operator in enumerate(operators):
+ operator_description = self._load_operator_description(id + 1, operator, path)
+ operators_description += f"{operator_description}\n"
+ return operators_description
+
+ def _load_operator_description(self, id: int, operator_name: str, file_path: str) -> str:
+ with open(file_path, "r") as f:
+ operator_data = json.load(f)
+ matched_data = operator_data[operator_name]
+ desc = matched_data["description"]
+ interface = matched_data["interface"]
+ return f"{id}. {operator_name}: {desc}, with interface {interface})."
+
+ def create_graph_optimize_prompt(
+ self,
+ experience: str,
+ score: float,
+ graph: str,
+ prompt: str,
+ operator_description: str,
+ type: str,
+ log_data: str,
+ ) -> str:
+ graph_input = WORKFLOW_INPUT.format(
+ experience=experience,
+ score=score,
+ graph=graph,
+ prompt=prompt,
+ operator_description=operator_description,
+ type=type,
+ log=log_data,
+ )
+ graph_system = WORKFLOW_OPTIMIZE_PROMPT.format(type=type)
+ return graph_input + WORKFLOW_CUSTOM_USE + graph_system
+
+ async def get_graph_optimize_response(self, graph_optimize_node):
+ max_retries = 5
+ retries = 0
+
+ while retries < max_retries:
+ try:
+ response = graph_optimize_node.instruct_content.model_dump()
+ return response
+ except Exception as e:
+ retries += 1
+ logger.info(f"Error generating prediction: {e}. Retrying... ({retries}/{max_retries})")
+ if retries == max_retries:
+ logger.info("Maximum retries reached. Skipping this sample.")
+ break
+ traceback.print_exc()
+ time.sleep(5)
+ return None
+
+ def write_graph_files(self, directory: str, response: dict, round_number: int, dataset: str):
+ graph = WORKFLOW_TEMPLATE.format(graph=response["graph"], round=round_number, dataset=dataset)
+
+ with open(os.path.join(directory, "graph.py"), "w", encoding="utf-8") as file:
+ file.write(graph)
+
+ with open(os.path.join(directory, "prompt.py"), "w", encoding="utf-8") as file:
+ file.write(response["prompt"])
+
+ with open(os.path.join(directory, "__init__.py"), "w", encoding="utf-8") as file:
+ file.write("")
diff --git a/metagpt/ext/aflow/scripts/prompts/optimize_prompt.py b/metagpt/ext/aflow/scripts/prompts/optimize_prompt.py
new file mode 100644
index 000000000..a2e862ec2
--- /dev/null
+++ b/metagpt/ext/aflow/scripts/prompts/optimize_prompt.py
@@ -0,0 +1,59 @@
+WORKFLOW_OPTIMIZE_PROMPT = """You are building a Graph and corresponding Prompt to jointly solve {type} problems.
+Referring to the given graph and prompt, which forms a basic example of a {type} solution approach,
+please reconstruct and optimize them. You can add, modify, or delete nodes, parameters, or prompts. Include your
+single modification in XML tags in your reply. Ensure they are complete and correct to avoid runtime failures. When
+optimizing, you can incorporate critical thinking methods like review, revise, ensemble (generating multiple answers through different/similar prompts, then voting/integrating/checking the majority to obtain a final answer), selfAsk, etc. Consider
+Python's loops (for, while, list comprehensions), conditional statements (if-elif-else, ternary operators),
+or machine learning techniques (e.g., linear regression, decision trees, neural networks, clustering). The graph
+complexity should not exceed 10. Use logical and control flow (IF-ELSE, loops) for a more enhanced graphical
+representation.Ensure that all the prompts required by the current graph from prompt_custom are included.Exclude any other prompts.
+Output the modified graph and all the necessary Prompts in prompt_custom (if needed).
+The prompt you need to generate is only the one used in `prompt_custom.XXX` within Custom. Other methods already have built-in prompts and are prohibited from being generated. Only generate those needed for use in `prompt_custom`; please remove any unused prompts in prompt_custom.
+the generated prompt must not contain any placeholders.
+Considering information loss, complex graphs may yield better results, but insufficient information transmission can omit the solution. It's crucial to include necessary context during the process."""
+
+
+WORKFLOW_INPUT = """
+Here is a graph and the corresponding prompt (prompt only related to the custom method) that performed excellently in a previous iteration (maximum score is 1). You must make further optimizations and improvements based on this graph. The modified graph must differ from the provided example, and the specific differences should be noted within the xxx section.\n
+
+ {experience}
+ (such as:add /delete /modify/ ...)
+ {score}
+ {graph}
+ {prompt}(only prompt_custom)
+ {operator_description}
+
+Below are the logs of some results with the aforementioned Graph that performed well but encountered errors, which can be used as references for optimization:
+{log}
+
+First, provide optimization ideas. **Only one detail point can be modified at a time**, and no more than 5 lines of code may be changed per modification—extensive modifications are strictly prohibited to maintain project focus!
+When introducing new functionalities in the graph, please make sure to import the necessary libraries or modules yourself, except for operator, prompt_custom, create_llm_instance, and CostManage, which have already been automatically imported.
+**Under no circumstances should Graph output None for any field.**
+Use custom methods to restrict your output format, rather than using code (outside of the code, the system will extract answers based on certain rules and score them).
+It is very important to format the Graph output answers, you can refer to the standard answer format in the log.
+"""
+
+WORKFLOW_CUSTOM_USE = """\nHere's an example of using the `custom` method in graph:
+```
+# You can write your own prompt in prompt_custom and then use it in the Custom method in the graph
+response = await self.custom(input=problem, instruction=prompt_custom.XXX_PROMPT)
+# You can also concatenate previously generated string results in the input to provide more comprehensive contextual information.
+# response = await self.custom(input=problem+f"xxx:{xxx}, xxx:{xxx}", instruction=prompt_custom.XXX_PROMPT)
+# The output from the Custom method can be placed anywhere you need it, as shown in the example below
+solution = await self.generate(problem=f"question:{problem}, xxx:{response['response']}")
+```
+Note: In custom, the input and instruction are directly concatenated(instruction+input), and placeholders are not supported. Please ensure to add comments and handle the concatenation externally.\n
+
+**Introducing multiple operators at appropriate points can enhance performance. If you find that some provided operators are not yet used in the graph, try incorporating them.**
+"""
+
+WORKFLOW_TEMPLATE = """from typing import Literal
+import metagpt.ext.aflow.scripts.optimized.{dataset}.workflows.template.operator as operator
+import metagpt.ext.aflow.scripts.optimized.{dataset}.workflows.round_{round}.prompt as prompt_custom
+from metagpt.provider.llm_provider_registry import create_llm_instance
+from metagpt.utils.cost_manager import CostManager
+
+DatasetType = Literal["HumanEval", "MBPP", "GSM8K", "MATH", "HotpotQA", "DROP"]
+
+{graph}
+"""
diff --git a/metagpt/ext/aflow/scripts/prompts/prompt.py b/metagpt/ext/aflow/scripts/prompts/prompt.py
new file mode 100644
index 000000000..16bf78af8
--- /dev/null
+++ b/metagpt/ext/aflow/scripts/prompts/prompt.py
@@ -0,0 +1,89 @@
+# -*- coding: utf-8 -*-
+# @Date : 6/26/2024 17:07 PM
+# @Author : didi
+# @Desc : prompts of operators
+
+ANSWER_GENERATION_PROMPT = """
+Think step by step and solve the problem.
+1. In the "thought" field, explain your thinking process in detail.
+2. In the "answer" field, provide the final answer concisely and clearly. The answer should be a direct response to the question, without including explanations or reasoning.
+Your task: {input}
+"""
+
+FORMAT_PROMPT = """
+For the question described as {problem_description},
+please extract a short and concise answer contains only one word/few words from the following solution: {solution}.
+Make sure there are no additional comments or explanations in your response.
+"""
+
+SC_ENSEMBLE_PROMPT = """
+Given the question described as follows: {question}
+Several solutions have been generated to address the given question. They are as follows:
+{solutions}
+
+Carefully evaluate these solutions and identify the answer that appears most frequently across them. This consistency in answers is crucial for determining the most reliable solution.
+
+In the "thought" field, provide a detailed explanation of your thought process. In the "solution_letter" field, output only the single letter ID (A, B, C, etc.) corresponding to the most consistent solution. Do not include any additional text or explanation in the "solution_letter" field.
+"""
+
+PYTHON_CODE_VERIFIER_PROMPT = """
+You are a professional Python programmer. Your task is to write complete, self-contained code based on a given mathematical problem and output the answer. The code should include all necessary imports and dependencies, and be ready to run without additional setup or environment configuration.
+
+Problem description: {problem}
+Other analysis: {analysis}
+{feedback}
+
+Your code should:
+1. Implement the calculation steps described in the problem.
+2. Define a function named `solve` that performs the calculation and returns the result. The `solve` function should not require any input parameters; instead, it should obtain all necessary inputs from within the function or from globally defined variables.
+3. `solve` function return the final calculation result.
+
+Please ensure your code is efficient, well-commented, and follows Python best practices. The output should be limited to basic data types such as strings, integers, and floats. It is prohibited to transmit images or other file formats. The code output is intended for a text-based language model.
+"""
+
+
+REFLECTION_ON_PUBLIC_TEST_PROMPT = """
+Given a code problem and a python code solution which failed to pass test or execute, you need to analyze the reason for the failure and propose a better code solution.:
+### problem
+{problem}
+
+### Code Solution
+{solution}
+
+### Execution Result
+{exec_pass}
+
+#### Failed Test Case
+{test_fail}
+
+Please provide a reflection on the failed test cases and code solution, followed by a better code solution without any additional text or test cases.
+"""
+
+MD_ENSEMBLE_PROMPT = """
+Given the question described as follows: {question}
+Several solutions have been generated to address the given question. They are as follows:
+{solutions}
+
+Carefully evaluate these solutions and identify the solution that is more capable of solving the problem compared to other solutions, as this is crucial for problem-solving.
+
+In the "thought" field, provide a detailed explanation of your thought process. In the "solution_letter" field, output only the single letter ID (A, B, C, etc.) corresponding to the solution. Do not include any additional text or explanation in the "solution_letter" field.
+"""
+
+REVIEW_PROMPT = """
+Given a problem and a thoughtful solution, your task is to using critical thinking (questioning) to review the solution's correctness and provide a review result in boolean format.
+
+problem: {problem}
+solution: {solution}
+
+If you are more than 95 percent confident that the final answer is incorrect, please return False and give a feedback for the error. Otherwise, please return True and give a explanation for the correctness.
+"""
+
+REVISE_PROMPT = """
+Given a problem and a thoughtful solution which is just reviewed as incorrect, your task is to revise the solution to solve the question and ensure the final code solution is wrapped with ```python```.
+
+problem: {problem}
+solution: {solution}
+feedback: {feedback}
+
+Ensure the output code is self-contained, and without any additional text or test cases.
+"""
diff --git a/metagpt/ext/aflow/scripts/utils.py b/metagpt/ext/aflow/scripts/utils.py
new file mode 100644
index 000000000..5e6222dc4
--- /dev/null
+++ b/metagpt/ext/aflow/scripts/utils.py
@@ -0,0 +1,125 @@
+"""
+@Time : 2024/7/24 16:37
+@Author : didi
+@File : utils.py
+"""
+
+import json
+import re
+from enum import Enum
+from typing import Any, List, Tuple
+
+
+class CodeDataset(Enum):
+ HUMAN_EVAL = "HumanEval"
+ MBPP = "MBPP"
+
+
+def extract_test_cases_from_jsonl(entry_point: str, dataset: CodeDataset = CodeDataset.HUMAN_EVAL):
+ if dataset == CodeDataset.HUMAN_EVAL.value:
+ file_path = "metagpt/ext/aflow/data/humaneval_public_test.jsonl"
+ # Retain the original hardcoded test cases
+ hardcoded_cases = {
+ "find_zero": "",
+ "decode_cyclic": "",
+ "decode_shift": "",
+ "by_length": "",
+ "add": "",
+ "triangle_area": "",
+ "correct_bracketing": "",
+ "solve": "",
+ "sum_squares": "",
+ "starts_one_ends": "",
+ }
+ elif dataset == CodeDataset.MBPP.value:
+ file_path = "metagpt/ext/aflow/data/mbpp_public_test.jsonl"
+ hardcoded_cases = {
+ "remove_odd": "",
+ "replace_spaces": "",
+ "snake_to_camel": "",
+ "Split": "",
+ "swap_List": "",
+ "square_Sum": "",
+ "sort_sublists": "",
+ "unique_sublists": "",
+ }
+ # Check if there are hardcoded test cases
+ if entry_point in hardcoded_cases:
+ return hardcoded_cases[entry_point]
+
+ # If there are no hardcoded test cases, read from the file
+ with open(file_path, "r") as file:
+ for line in file:
+ data = json.loads(line)
+ if data.get("entry_point") == entry_point:
+ return data.get("test")
+
+ return None
+
+
+def extract_test_cases(docstring: str) -> List[Tuple[str, List[Any], Any]]:
+ # Use regular expressions to match test cases, now capturing function names and any output
+ pattern = r">>> (\w+)\((.*?)\)\n\s*(.*?)(?=\n|$)"
+ matches = re.findall(pattern, docstring, re.DOTALL)
+
+ test_cases = []
+ for match in matches:
+ func_name, input_str, expected_output = match
+
+ # Process input
+ input_list = []
+ for item in input_str.split(","):
+ item = item.strip()
+ try:
+ # Try to convert input to numeric type
+ if "." in item:
+ input_list.append(float(item))
+ else:
+ input_list.append(int(item))
+ except ValueError:
+ # If unable to convert to numeric, keep as string
+ input_list.append(item.strip("'\""))
+
+ # Process output
+ try:
+ # Try to convert output to numeric or boolean value
+ if expected_output.lower() == "true":
+ expected_output = True
+ elif expected_output.lower() == "false":
+ expected_output = False
+ elif "." in expected_output:
+ expected_output = float(expected_output)
+ else:
+ expected_output = int(expected_output)
+ except ValueError:
+ # If unable to convert, keep as string
+ expected_output = expected_output.strip("'\"")
+
+ test_cases.append([func_name, input_list, expected_output])
+
+ return test_cases
+
+
+def test_cases_2_test_functions(solution: str, test_cases: str):
+ tester_function = f"""
+{solution}
+
+{test_cases}
+"""
+ return tester_function
+
+
+def test_case_2_test_function(solution: str, test_case: str, entry_point: str):
+ tester_function = f"""
+{solution}
+
+
+def check(candidate):
+ {test_case}
+
+def test_check():
+ check({entry_point})
+
+test_check()
+"""
+ return tester_function
diff --git a/metagpt/ext/aflow/scripts/workflow.py b/metagpt/ext/aflow/scripts/workflow.py
new file mode 100644
index 000000000..47b54021b
--- /dev/null
+++ b/metagpt/ext/aflow/scripts/workflow.py
@@ -0,0 +1,28 @@
+# -*- coding: utf-8 -*-
+# @Date : 6/27/2024 22:07 PM
+# @Author : didi
+# @Desc : Basic Graph Class
+
+
+from metagpt.ext.aflow.scripts.evaluator import DatasetType
+from metagpt.provider.llm_provider_registry import create_llm_instance
+from metagpt.utils.cost_manager import CostManager
+
+
+class Workflow:
+ def __init__(
+ self,
+ name: str,
+ llm_config,
+ dataset: DatasetType,
+ ) -> None:
+ self.name = name
+ self.dataset = dataset
+ self.llm = create_llm_instance(llm_config)
+ self.llm.cost_manager = CostManager()
+
+ async def __call__(self, problem: str):
+ """
+ Implementation of the workflow
+ """
+ raise NotImplementedError("This method should be implemented by the subclass")
diff --git a/metagpt/ext/android_assistant/README.md b/metagpt/ext/android_assistant/README.md
new file mode 100644
index 000000000..fe8b4b3e3
--- /dev/null
+++ b/metagpt/ext/android_assistant/README.md
@@ -0,0 +1,118 @@
+# MetaGPT Android Assistant
+
+The MetaGPT Android Assistant is an intelligent assistance tool driven by a multi-modal large language model based on the advanced MetaGPT framework. It has the ability to self-learn, mastering users' daily usage patterns through learning, and can automatically complete various application operations according to user instructions, achieving comprehensive liberation of users' hands.
+Next, we will introduce the functions of the MetaGPT Android Assistant and how to use it.
+
+## Features
+
+The operation of the MetaGPT Android Assistant mainly includes two stages: learning and automatic execution. Below, we introduce the specific features of the MetaGPT Android Assistant from these two stages.
+
+### Learning Stage
+
+By learning from human demonstrations or exploring apps based on human instructions, the MetaGPT Android Assistant can learn the functionality of apps, generate corresponding operation documents for use in the subsequent "automatic execution" stage. Approximately 20 rounds of exploration for any given task objective can significantly improve performance.
+
+By setting the `stage` to `learn`, you can ask the Android Assistant to enter the learning stage. By setting the `mode` to `auto`, you can instruct the Android Assistant to learn through automatic exploration; by setting the mode to manual, you can instruct the Android Assistant to learn through human manual demonstration. In the usage section, we provide detailed explanations of the script parameters. You can try experimenting with automatic exploration and manual demonstration modes on the "Messenger" app with the following commands:
+
+```bash
+cd examples/android_assistant
+python run_assistant.py "Send 'When will we release this feature?' to +86 8888888" --stage "learn" --mode "auto or manual" --app-name "Messenger"
+```
+
+#### Learning Based on Human Demonstration
+When asking the Android Assistant to perform self-exploration during the learning stage, you can free your hands. However, when instructing it to learn according to your commands, you need to follow the instructions in the terminal for the Android Assistant to accurately learn your operation methods.
+A possible example is as follows:
+
+```bash
+cd examples/android_assistant
+python run_assistant.py "Send 'When will we release this feature?' to +86 8888888" --stage "learn" --mode "manual" --app-name "Messenger"
+```
+
+After running this command, you will first see a screenshot of an Android screen that has been marked at various interactive locations, as shown in the figure below:
+
+
+
+After remembering the location where you want to operate, a request similar to the one below will be output in the terminal. Reply to it and thereby direct the Android assistant to learn your demonstration action:
+
+```bash
+| INFO | examples.android_assistant.actions.manual_record:run:96 - Which element do you want to tap? Choose a numeric tag from 1 to 11:
+user_input: 8
+| INFO | examples.android_assistant.actions.manual_record:run:81 - Choose one of the following actions you want to perform on the current screen:
+tap, text, long_press, swipe, stop
+user_input: tap
+```
+
+### Automatic Execution Stage
+After the Android Assistant completes the learning stage, you can command it to complete tasks on the phone through text descriptions. By configuring the operation documents from the self-learning stage, the Android Assistant has richer prior knowledge, and its execution capabilities are further enhanced.
+You can instruct the Android Assistant to send messages in the "Messenger" app with the following command:
+```bash
+python run_assistant.py "Send 'When will we release this feature?' to +86 8888888" --stage "act" --mode "auto or manual" --app-name "Messenger"
+```
+Specifically, by selecting `auto` for `mode`, the Android assistant will employ the operational records compiled through self-exploration. Alternatively, if `manual` is chosen as the `mode`, the Android assistant will leverage the operation manuals accrued from learning via human demonstration.
+
+## Installation
+To use the Android Assistant, you first need to meet the following conditions:
+1. Complete the installation of the MetaGPT environment.
+2. Install [Android Debug Bridge (ADB)](https://developer.android.com/tools/adb?hl=zh-cn) on your PC, which enables interaction between your PC and Android devices.
+3. Install Android Studio and within it, install the Android emulator to provide an environment for the Android Assistant to learn and execute. For information on how to install the Android emulator, refer to [Quick Installation of Android Studio & Emulator](https://docs.expo.dev/workflow/android-studio-emulator/).
+4. (Optional) Connect your Android device to the USB port of your PC, which can also provide an environment for the Android Assistant to learn and execute.
+
+Note ⚠️: When operating with the Android emulator, the emulator model we use is Medium Phone, which is recommended for first-time users to complete the operation.
+
+After completing these operations, you can enter the following command to check if ADB is installed successfully and if the Android device is connected:
+```bash
+adb devices
+```
+
+## Usage
+The MetaGPT Android Assistant is designed within the MetaGPT framework as a collection of Roles and multiple Actions. You can run it by executing the `run_assistant.py` script. The specific parameter description of this script is as follows:
+```text
+Usage: run_assistant.py [OPTIONS] TASK_DESC
+
+ Run a Android Assistant
+
+Arguments:
+ TASK_DESC the task description you want the android assistant to learn or
+ act [required]
+
+Options:
+ --n-round INTEGER The max round to do an app operation task.
+ [default: 20]
+ --stage TEXT stage: learn / act [default: learn]
+ --mode TEXT mode: auto / manual , when state=learn
+ [default: auto]
+ --app-name TEXT the name of app you want to run [default:
+ demo]
+ --investment FLOAT Dollar amount to invest in the AI company.
+ [default: 5.0]
+ --refine-doc / --no-refine-doc Refine existing operation docs based on the
+ latest observation if True. [default: no-
+ refine-doc]
+ --min-dist INTEGER The minimum distance between elements to
+ prevent overlapping during the labeling
+ process. [default: 30]
+ --android-screenshot-dir TEXT The path to store screenshots on android
+ device. Make sure it exists. [default:
+ /sdcard/Pictures/Screenshots]
+ --android-xml-dir TEXT The path to store xml files for determining
+ UI elements localtion. Make sure it exists.
+ [default: /sdcard]
+ --device-id TEXT The Android device_id [default:
+ emulator-5554]
+ --help Show this message and exit.
+```
+
+## Acknowledgements
+The MetaGPT Android Assistant has referenced some ideas and code from the [AppAgent](https://github.com/mnotgod96/AppAgent) project. We thank the developers of the Appagent project.
+
+### Citation
+
+```bib
+@misc{yang2023appagent,
+ title={AppAgent: Multimodal Agents as Smartphone Users},
+ author={Chi Zhang and Zhao Yang and Jiaxuan Liu and Yucheng Han and Xin Chen and Zebiao Huang and Bin Fu and Gang Yu},
+ year={2023},
+ eprint={2312.13771},
+ archivePrefix={arXiv},
+ primaryClass={cs.CV}
+}
+```
\ No newline at end of file
diff --git a/metagpt/ext/android_assistant/README_CN.md b/metagpt/ext/android_assistant/README_CN.md
new file mode 100644
index 000000000..a1abbe3b0
--- /dev/null
+++ b/metagpt/ext/android_assistant/README_CN.md
@@ -0,0 +1,113 @@
+# MetaGPT 安卓助理
+
+MetaGPT安卓助理是一款依托于先进的MetaGPT框架构建的多模态大语言模型驱动的智能辅助工具。
+它具备自我学习的能力,能够通过学习掌握用户的日常使用方式,同时能够根据用户的指令自动完成各类应用程序的操作任务,实现了用户双手的全面解放。
+接下来,我们将介绍MetaGPT安卓助理的功能以及如何使用它。
+
+## 功能
+
+MetaGPT 安卓助理的执行主要包含两个阶段,分别为自我学习与自动执行。下面,我们将从这两个阶段介绍MetaGPT 安卓助理的具体功能。
+
+### 自我学习阶段
+
+通过学习人类演示或基于人类指令对app进行探索,MetaGPT安卓助理可以对app的功能进行学习,生成相应的操作文档,为后续的“自动执行”阶段使用。对于任何给定的任务目标,进行约20轮的探索可以显著提高性能。
+
+通过设定`stage`为`learn`可要求安卓助理进入自我学习阶段。通过设定`mode`为`auto`,可要求安卓助理通过自动探索学习,通过设定`mode`为`manual`,可要求安卓助理通过人类手动演示学习。在使用章节,我们对脚本的参数进行了详细的说明。
+您可以尝试对“Messenger”应用程序进行自动探索和手动演示模式的实验,具体命令如下:
+
+```bash
+cd examples/android_assistant
+python run_assistant.py "Send 'When will we release this feature? to +86 8888888'" --stage "learn" --mode "auto or manual" --app-name "Messenger"
+```
+
+#### 基于人类演示的学习
+在要求安卓助理在自我学习阶段执行自我探索时,您可以解放您的双手,但在要求他根据您的指令进行学习时,你需要根据终端中的指令进行输入,以便安卓助理能够准确地学习您的操作方式。
+一个可能的例子如下:
+
+```bash
+cd examples/android_assistant
+python run_assistant.py "Send 'When will we release this feature? to +86 8888888'" --stage "learn" --mode "manual" --app-name "Messenger"
+```
+
+在运行这一指令后,你将首先看到一个在各个可交互的位置进行了标记的安卓屏幕的截图,如下图:
+
+
+
+在记住你要操作的位置之后,终端中将会输出与下面类似的要求,回复它,进而指挥安卓助理学习你的演示行为:
+
+```bash
+| INFO | examples.android_assistant.actions.manual_record:run:96 - Which element do you want to tap? Choose a numeric tag from 1 to 11:
+user_input: 8
+| INFO | examples.android_assistant.actions.manual_record:run:81 - Choose one of the following actions you want to perform on the current screen:
+tap, text, long_press, swipe, stop
+user_input: tap
+```
+### 自动执行阶段
+在安卓助理完成了自我学习阶段之后,您可以通过文本描述的方式,指挥安卓助理在手机中完成任务。通过为其配置自我学习阶段的操作文档,安卓助理具备了更丰富的前置知识,执行能力进一步得到提升。
+你可以通过以下指令,指挥安卓助理在“Messenger”应用中发送信息:
+```bash
+python run_assistant.py "Send 'When will we release this feature? to +86 8888888'" --stage "act" --mode "auto or manual" --app-name "Messenger"
+```
+其中,`mode`选择`auto`,安卓助理将使用自我探索中积累的操作文档;`mode`选择`manual`,安卓助理将使用人类演示学习中积累的操作文档。
+
+## 安装
+为了使用安卓助理,你首先需要满足以下条件:
+1. 完成MetaGPT环境的安装
+2. 在你的PC上安装[Android Debug Bridge(ADB)](https://developer.android.com/tools/adb?hl=zh-cn),ADB可以使你的PC与安卓设备进行交互。
+3. 安装Android Studio,在其中安装Android模拟器,以为安卓助手提供学习与执行的环境。关于如何安装Android模拟器,可以参考[快速安装Android Studio & Emulator](https://dev.weixin.qq.com/docs/framework/dev/framework/env/android-simulator.html)。
+4. (Optional) 将你的安卓设备连接到PC的USB端口上,这同样可以为安卓助手提供学习与执行的环境。
+
+注意 ⚠️:在使用Android模拟器进行操作时,我们使用的模拟器型号为Medium Phone,建议第一次尝试此类应用的用户使用这一型号完成操作。
+
+在完成这一系列操作之后,你可以输入以下命令检查ADB是否安装成功,以及安卓设备是否连接
+```bash
+adb devices
+```
+## 使用
+MetaGPT 安卓助理在MetaGPT框架中被设计为一个`Role`与多个`Action`的集合,你可以通过运行`run_assistant.py`脚本来运行它。这一脚本具体的参数说明如下:
+```text
+用法:run_assistant.py [选项] 任务描述
+
+ 运行一个安卓助手
+
+参数:
+ TASK_DESC 你希望安卓助手学习或执行的任务描述
+ [必需]
+
+选项:
+ --n-round 整数 执行应用程序操作任务的最大轮数。
+ [默认值:20]
+ --stage 文本 阶段:learn/act [默认值:learn]
+ --mode 文本 模式:auto/manual,当状态=learn时 [默认值:auto]
+ --app-name 文本 你想要运行的应用程序名称 [默认值:
+ 演示]
+ --investment 浮点数 投资于人工智能公司的美元金额。
+ [默认值:5.0]
+ --refine-doc / --no-refine-doc 如果为真,则根据最新的观察结果优化现有操作文档。
+ [默认值:--no-refine-doc]
+ --min-dist 整数 在标记过程中防止元素重叠的最小元素间距。
+ [默认值:30]
+ --android-screenshot-dir 文本 在安卓设备上存储截图的路径。确保其存在。
+ [默认值:/sdcard/Pictures/Screenshots]
+ --android-xml-dir 文本 存储用于确定UI元素位置的XML文件的路径。
+ 确保其存在。[默认值:/sdcard]
+ --device-id 文本 安卓device_id [默认值:
+ 模拟器-5554]
+ --help 显示此信息并退出。
+```
+
+## 致谢
+MetaGPT 安卓助理参考了 [AppAgent](https://github.com/mnotgod96/AppAgent) 项目的部分思路与代码,感谢 Appagent 项目的开发者们。
+
+### 引用
+
+```bib
+@misc{yang2023appagent,
+ title={AppAgent: Multimodal Agents as Smartphone Users},
+ author={Chi Zhang and Zhao Yang and Jiaxuan Liu and Yucheng Han and Xin Chen and Zebiao Huang and Bin Fu and Gang Yu},
+ year={2023},
+ eprint={2312.13771},
+ archivePrefix={arXiv},
+ primaryClass={cs.CV}
+}
+```
\ No newline at end of file
diff --git a/metagpt/ext/android_assistant/__init__.py b/metagpt/ext/android_assistant/__init__.py
new file mode 100644
index 000000000..2bcf8efd0
--- /dev/null
+++ b/metagpt/ext/android_assistant/__init__.py
@@ -0,0 +1,3 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# @Desc :
diff --git a/metagpt/ext/android_assistant/actions/__init__.py b/metagpt/ext/android_assistant/actions/__init__.py
new file mode 100644
index 000000000..2bcf8efd0
--- /dev/null
+++ b/metagpt/ext/android_assistant/actions/__init__.py
@@ -0,0 +1,3 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# @Desc :
diff --git a/metagpt/ext/android_assistant/actions/manual_record.py b/metagpt/ext/android_assistant/actions/manual_record.py
new file mode 100644
index 000000000..bcfb2ed89
--- /dev/null
+++ b/metagpt/ext/android_assistant/actions/manual_record.py
@@ -0,0 +1,168 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# @Desc : manual record user interaction in stage=learn & mode=manual, LIKE scripts/step_recorder.py
+import time
+from pathlib import Path
+
+import cv2
+
+from metagpt.actions.action import Action
+from metagpt.config2 import config
+from metagpt.environment.android.android_env import AndroidEnv
+from metagpt.environment.android.const import ADB_EXEC_FAIL
+from metagpt.environment.android.env_space import (
+ EnvAction,
+ EnvActionType,
+ EnvObsParams,
+ EnvObsType,
+)
+from metagpt.ext.android_assistant.utils.schema import (
+ ActionOp,
+ AndroidActionOutput,
+ RunState,
+ SwipeOp,
+)
+from metagpt.ext.android_assistant.utils.utils import (
+ draw_bbox_multi,
+ elem_list_from_xml_tree,
+)
+from metagpt.logs import logger
+
+
+class ManualRecord(Action):
+ """do a human operation on the screen with human input"""
+
+ name: str = "ManualRecord"
+
+ useless_list: list[str] = [] # store useless elements uid
+ record_path: Path = ""
+ task_desc_path: Path = ""
+ screenshot_before_path: Path = ""
+ screenshot_after_path: Path = ""
+ xml_path: Path = ""
+
+ async def run(self, task_desc: str, task_dir: Path, env: AndroidEnv):
+ self.record_path = Path(task_dir) / "record.txt"
+ self.task_desc_path = Path(task_dir) / "task_desc.txt"
+ self.screenshot_before_path = Path(task_dir) / "raw_screenshots"
+ self.screenshot_after_path = Path(task_dir) / "labeled_screenshots"
+ self.xml_path = Path(task_dir) / "xml"
+ for path in [self.screenshot_before_path, self.screenshot_after_path, self.xml_path]:
+ path.mkdir(parents=True, exist_ok=True)
+
+ self.record_path.write_text("")
+ record_file = open(self.record_path, "w")
+ self.task_desc_path.write_text(task_desc)
+
+ step = 0
+ extra_config = config.extra
+ while True:
+ step += 1
+ screenshot_path: Path = env.observe(
+ EnvObsParams(
+ obs_type=EnvObsType.GET_SCREENSHOT, ss_name=f"{step}", local_save_dir=self.screenshot_before_path
+ )
+ )
+ xml_path: Path = env.observe(
+ EnvObsParams(obs_type=EnvObsType.GET_XML, xml_name=f"{step}", local_save_dir=self.xml_path)
+ )
+ if not screenshot_path.exists() or not xml_path.exists():
+ return AndroidActionOutput(action_state=RunState.FAIL)
+
+ elem_list = elem_list_from_xml_tree(xml_path, self.useless_list, extra_config.get("min_dist", 30))
+
+ screenshot_labeled_path = Path(self.screenshot_after_path).joinpath(f"{step}_labeled.png")
+ labeled_img = draw_bbox_multi(screenshot_path, screenshot_labeled_path, elem_list)
+
+ cv2.namedWindow("image", cv2.WINDOW_NORMAL)
+ cv2.imshow("image", labeled_img)
+ cv2.waitKey(0)
+ cv2.destroyAllWindows()
+
+ user_input = "xxx"
+ logger.info(
+ "Choose one of the following actions you want to perform on the current screen:\n"
+ "tap, text, long_press, swipe, stop"
+ )
+
+ while (
+ user_input.lower() != ActionOp.TAP.value
+ and user_input.lower() != ActionOp.TEXT.value
+ and user_input.lower() != ActionOp.LONG_PRESS.value
+ and user_input.lower() != ActionOp.SWIPE.value
+ and user_input.lower() != ActionOp.STOP.value
+ ):
+ user_input = input("user_input: ")
+
+ if user_input.lower() == ActionOp.TAP.value:
+ logger.info(f"Which element do you want to tap? Choose a numeric tag from 1 to {len(elem_list)}:")
+ user_input = "xxx"
+ while not user_input.isnumeric() or int(user_input) > len(elem_list) or int(user_input) < 1:
+ user_input = input("user_input: ")
+ tl, br = elem_list[int(user_input) - 1].bbox
+ x, y = (tl[0] + br[0]) // 2, (tl[1] + br[1]) // 2
+ action = EnvAction(action_type=EnvActionType.SYSTEM_TAP, coord=(x, y))
+ log_str = f"tap({int(user_input)}):::{elem_list[int(user_input) - 1].uid}\n"
+ elif user_input.lower() == ActionOp.TEXT.value:
+ logger.info(
+ f"Which element do you want to input the text string? Choose a numeric tag from 1 to "
+ f"{len(elem_list)}:"
+ )
+ input_area = "xxx"
+ while not input_area.isnumeric() or int(input_area) > len(elem_list) or int(input_area) < 1:
+ input_area = input("user_input: ")
+ logger.info("Enter your input text below:")
+ user_input = ""
+ while not user_input:
+ user_input = input("user_input: ")
+ action = EnvAction(action_type=EnvActionType.USER_INPUT, input_txt=user_input)
+ log_str = f"text({input_area}:sep:'{user_input}'):::{elem_list[int(input_area) - 1].uid}\n"
+ elif user_input.lower() == ActionOp.LONG_PRESS.value:
+ logger.info(
+ f"Which element do you want to long press? Choose a numeric tag from 1 to {len(elem_list)}:"
+ )
+ user_input = "xxx"
+ while not user_input.isnumeric() or int(user_input) > len(elem_list) or int(user_input) < 1:
+ user_input = input("user_input: ")
+ tl, br = elem_list[int(user_input) - 1].bbox
+ x, y = (tl[0] + br[0]) // 2, (tl[1] + br[1]) // 2
+ action = EnvAction(action_type=EnvActionType.USER_LONGPRESS, coord=(x, y))
+ log_str = f"long_press({int(user_input)}):::{elem_list[int(user_input) - 1].uid}\n"
+ elif user_input.lower() == ActionOp.SWIPE.value:
+ logger.info(
+ "What is the direction of your swipe? Choose one from the following options:\n"
+ "up, down, left, right"
+ )
+ user_input = ""
+ while (
+ user_input != SwipeOp.UP.value
+ and user_input != SwipeOp.DOWN.value
+ and user_input != SwipeOp.LEFT.value
+ and user_input != SwipeOp.RIGHT.value
+ ):
+ user_input = input("user_input: ")
+ swipe_dir = user_input
+ logger.info(f"Which element do you want to swipe? Choose a numeric tag from 1 to {len(elem_list)}:")
+ while not user_input.isnumeric() or int(user_input) > len(elem_list) or int(user_input) < 1:
+ user_input = input("user_input: ")
+ tl, br = elem_list[int(user_input) - 1].bbox
+ x, y = (tl[0] + br[0]) // 2, (tl[1] + br[1]) // 2
+
+ action = EnvAction(action_type=EnvActionType.USER_SWIPE, coord=(x, y), orient=swipe_dir)
+ log_str = f"swipe({int(user_input)}:sep:{swipe_dir}):::{elem_list[int(user_input) - 1].uid}\n"
+ elif user_input.lower() == ActionOp.STOP.value:
+ record_file.write("stop\n")
+ record_file.close()
+ break
+ else:
+ break
+
+ obs, _, _, _, info = env.step(action)
+ action_res = info["res"]
+ if action_res == ADB_EXEC_FAIL:
+ return AndroidActionOutput(action_state=RunState.FAIL)
+ record_file.write(log_str)
+
+ time.sleep(1)
+
+ return AndroidActionOutput(action_state=RunState.SUCCESS)
diff --git a/metagpt/ext/android_assistant/actions/parse_record.py b/metagpt/ext/android_assistant/actions/parse_record.py
new file mode 100644
index 000000000..304daf655
--- /dev/null
+++ b/metagpt/ext/android_assistant/actions/parse_record.py
@@ -0,0 +1,137 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# @Desc : parse record to generate learned standard operations in stage=learn & mode=manual,
+# LIKE scripts/document_generation.py
+
+import ast
+import re
+from pathlib import Path
+
+from metagpt.actions.action import Action
+from metagpt.config2 import config
+from metagpt.ext.android_assistant.actions.parse_record_an import RECORD_PARSE_NODE
+from metagpt.ext.android_assistant.prompts.operation_prompt import (
+ long_press_doc_template,
+ refine_doc_suffix,
+ swipe_doc_template,
+ tap_doc_template,
+ text_doc_template,
+)
+from metagpt.ext.android_assistant.utils.schema import (
+ ActionOp,
+ AndroidActionOutput,
+ RecordLogItem,
+ RunState,
+ SwipeOp,
+)
+from metagpt.logs import logger
+from metagpt.utils.common import encode_image
+
+
+class ParseRecord(Action):
+ name: str = "ParseRecord"
+ record_path: Path = ""
+ task_desc_path: Path = ""
+ screenshot_before_path: Path = ""
+ screenshot_after_path: Path = ""
+
+ async def run(self, task_dir: Path, docs_dir: Path):
+ doc_count = 0
+ self.record_path = Path(task_dir) / "record.txt"
+ self.task_desc_path = Path(task_dir) / "task_desc.txt"
+ self.screenshot_before_path = Path(task_dir) / "raw_screenshots"
+ self.screenshot_after_path = Path(task_dir) / "labeled_screenshots"
+ for path in [self.screenshot_before_path, self.screenshot_after_path]:
+ path.mkdir(parents=True, exist_ok=True)
+
+ task_desc = self.task_desc_path.read_text()
+ extra_config = config.extra
+
+ with open(self.record_path, "r") as record_file:
+ record_step_count = len(record_file.readlines()) - 1
+ record_file.seek(0)
+ for step in range(1, record_step_count + 1):
+ img_before_base64 = encode_image(self.screenshot_after_path.joinpath(f"{step}_labeled.png"))
+ img_after_base64 = encode_image(self.screenshot_after_path.joinpath(f"{step + 1}_labeled.png"))
+ rec = record_file.readline().strip()
+ action, resource_id = rec.split(":::")
+ action_type = action.split("(")[0]
+ # 构建Prompt
+ action_param = re.findall(r"\((.*?)\)", action)[0]
+ if action_type == ActionOp.TAP.value:
+ prompt_template = tap_doc_template
+ context = prompt_template.format(ui_element=action_param)
+ elif action_type == ActionOp.TEXT.value:
+ input_area, input_text = action_param.split(":sep:")
+ prompt_template = text_doc_template
+ context = prompt_template.format(ui_element=input_area)
+ elif action_type == ActionOp.LONG_PRESS.value:
+ prompt_template = long_press_doc_template
+ context = prompt_template.format(ui_element=action_param)
+ elif action_type == ActionOp.SWIPE.value:
+ swipe_area, swipe_dir = action_param.split(":sep:")
+ if swipe_dir == SwipeOp.UP.value or swipe_dir == SwipeOp.DOWN.value:
+ action_type = ActionOp.VERTICAL_SWIPE.value
+ elif swipe_dir == SwipeOp.LEFT.value or swipe_dir == SwipeOp.RIGHT.value:
+ action_type = ActionOp.HORIZONTAL_SWIPE.value
+ prompt_template = swipe_doc_template
+ context = prompt_template.format(swipe_dir=swipe_dir, ui_element=swipe_area)
+ else:
+ break
+ context = context.format(task_desc=task_desc)
+
+ doc_name = resource_id + ".txt"
+ doc_path = docs_dir.joinpath(doc_name)
+
+ if doc_path.exists():
+ try:
+ doc_content = ast.literal_eval(doc_path.read_text())
+ except Exception as exp:
+ logger.error(f"ast parse doc: {doc_path} failed, exp: {exp}")
+ continue
+
+ if doc_content[action_type]:
+ if extra_config.get("doc_refine", False):
+ refine_context = refine_doc_suffix.format(old_doc=doc_content[action_type])
+ context += refine_context
+ logger.info(
+ f"Documentation for the element {resource_id} already exists. The doc will be "
+ f"refined based on the latest demo."
+ )
+ else:
+ logger.info(
+ f"Documentation for the element {resource_id} already exists. Turn on DOC_REFINE "
+ f"in the config file if needed."
+ )
+ continue
+ else:
+ doc_content = {"tap": "", "text": "", "v_swipe": "", "h_swipe": "", "long_press": ""}
+
+ logger.info(f"Waiting for GPT-4V to generate documentation for the element {resource_id}")
+ node = await RECORD_PARSE_NODE.fill(
+ context=context, llm=self.llm, images=[img_before_base64, img_after_base64]
+ )
+ if "error" in node.content:
+ return AndroidActionOutput(action_state=RunState.FAIL)
+ log_path = task_dir.joinpath("log_parse_record.txt")
+ prompt = node.compile(context=context, schema="json", mode="auto")
+ msg = node.content
+ doc_content[action_type] = msg
+
+ with open(log_path, "a") as logfile:
+ log_item = RecordLogItem(
+ step=step,
+ prompt=prompt,
+ image_before=img_before_base64,
+ image_after=img_after_base64,
+ response=node.content,
+ )
+ logfile.write(log_item.model_dump_json() + "\n")
+ with open(doc_path, "w") as outfile:
+ outfile.write(str(doc_content))
+ doc_count += 1
+ logger.info(f"Documentation generated and saved to {doc_path}")
+
+ logger.info(f"Documentation generation phase completed. {doc_count} docs generated.")
+
+ return AndroidActionOutput(action_state=RunState.FINISH)
diff --git a/metagpt/ext/android_assistant/actions/parse_record_an.py b/metagpt/ext/android_assistant/actions/parse_record_an.py
new file mode 100644
index 000000000..210c93e23
--- /dev/null
+++ b/metagpt/ext/android_assistant/actions/parse_record_an.py
@@ -0,0 +1,32 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# @Desc : the ActionNode to parse record
+
+from metagpt.actions.action_node import ActionNode
+
+OBSERVATION = ActionNode(
+ key="Observation",
+ expected_type=str,
+ instruction="Provide a description of your observations of the two images. "
+ "Subsequently, delineate the distinctions between the first image and the second one.",
+ example="",
+)
+
+THOUGHT = ActionNode(
+ key="Thought",
+ expected_type=str,
+ instruction="Consider the impact of Action acting on UI elements.",
+ example="",
+)
+
+DESCRIPTION = ActionNode(
+ key="Description",
+ expected_type=str,
+ instruction="Describe the functionality of the UI element concisely in one or two sentences Do not include "
+ "the numeric tag in your description",
+ example="",
+)
+
+NODES = [OBSERVATION, THOUGHT, DESCRIPTION]
+
+RECORD_PARSE_NODE = ActionNode.from_children("RecordParse", NODES)
diff --git a/metagpt/ext/android_assistant/actions/screenshot_parse.py b/metagpt/ext/android_assistant/actions/screenshot_parse.py
new file mode 100644
index 000000000..4d8bb0e1e
--- /dev/null
+++ b/metagpt/ext/android_assistant/actions/screenshot_parse.py
@@ -0,0 +1,204 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# @Desc : LIKE scripts/task_executor.py in stage=act
+
+import ast
+from pathlib import Path
+
+from metagpt.actions.action import Action
+from metagpt.config2 import config
+from metagpt.environment.android.android_env import AndroidEnv
+from metagpt.environment.android.const import ADB_EXEC_FAIL
+from metagpt.environment.android.env_space import (
+ EnvAction,
+ EnvActionType,
+ EnvObsParams,
+ EnvObsType,
+)
+from metagpt.ext.android_assistant.actions.screenshot_parse_an import (
+ SCREENSHOT_PARSE_NODE,
+)
+from metagpt.ext.android_assistant.prompts.assistant_prompt import (
+ screenshot_parse_template,
+ screenshot_parse_with_grid_template,
+)
+from metagpt.ext.android_assistant.utils.schema import (
+ AndroidActionOutput,
+ AndroidElement,
+ GridOpParam,
+ LongPressGridOpParam,
+ LongPressOpParam,
+ OpLogItem,
+ RunState,
+ SwipeGridOpParam,
+ SwipeOpParam,
+ TapGridOpParam,
+ TapOpParam,
+ TextOpParam,
+)
+from metagpt.ext.android_assistant.utils.utils import (
+ area_to_xy,
+ draw_bbox_multi,
+ draw_grid,
+ elem_bbox_to_xy,
+ screenshot_parse_extract,
+ traverse_xml_tree,
+)
+from metagpt.logs import logger
+from metagpt.utils.common import encode_image
+
+
+class ScreenshotParse(Action):
+ name: str = "ScreenshotParse"
+
+ def _makeup_ui_document(self, elem_list: list[AndroidElement], docs_idr: Path, use_exist_doc: bool = True) -> str:
+ if not use_exist_doc:
+ return ""
+
+ ui_doc = """
+You also have access to the following documentations that describes the functionalities of UI
+elements you can interact on the screen. These docs are crucial for you to determine the target of your
+next action. You should always prioritize these documented elements for interaction: """
+ for i, elem in enumerate(elem_list):
+ doc_path = docs_idr.joinpath(f"{elem.uid}.txt")
+ if not doc_path.exists():
+ continue
+ try:
+ doc_content = ast.literal_eval(doc_path.read_text())
+ except Exception as exp:
+ logger.error(f"ast parse doc: {doc_path} failed, exp: {exp}")
+ continue
+
+ ui_doc += f"Documentation of UI element labeled with the numeric tag '{i + 1}':\n"
+ if doc_content["tap"]:
+ ui_doc += f"This UI element is clickable. {doc_content['tap']}\n\n"
+ if doc_content["text"]:
+ ui_doc += (
+ f"This UI element can receive text input. The text input is used for the following "
+ f"purposes: {doc_content['text']}\n\n"
+ )
+ if doc_content["long_press"]:
+ ui_doc += f"This UI element is long clickable. {doc_content['long_press']}\n\n"
+ if doc_content["v_swipe"]:
+ ui_doc += (
+ f"This element can be swiped directly without tapping. You can swipe vertically on "
+ f"this UI element. {doc_content['v_swipe']}\n\n"
+ )
+ if doc_content["h_swipe"]:
+ ui_doc += (
+ f"This element can be swiped directly without tapping. You can swipe horizontally on "
+ f"this UI element. {doc_content['h_swipe']}\n\n"
+ )
+ return ui_doc
+
+ async def run(
+ self,
+ round_count: int,
+ task_desc: str,
+ last_act: str,
+ task_dir: Path,
+ docs_dir: Path,
+ grid_on: bool,
+ env: AndroidEnv,
+ ):
+ extra_config = config.extra
+ for path in [task_dir, docs_dir]:
+ path.mkdir(parents=True, exist_ok=True)
+ screenshot_path: Path = env.observe(
+ EnvObsParams(obs_type=EnvObsType.GET_SCREENSHOT, ss_name=f"{round_count}_before", local_save_dir=task_dir)
+ )
+ xml_path: Path = env.observe(
+ EnvObsParams(obs_type=EnvObsType.GET_XML, xml_name=f"{round_count}", local_save_dir=task_dir)
+ )
+ if not screenshot_path.exists() or not xml_path.exists():
+ return AndroidActionOutput(action_state=RunState.FAIL)
+
+ clickable_list = []
+ focusable_list = []
+ traverse_xml_tree(xml_path, clickable_list, "clickable", True)
+ traverse_xml_tree(xml_path, focusable_list, "focusable", True)
+ elem_list: list[AndroidElement] = clickable_list.copy()
+ for elem in focusable_list:
+ bbox = elem.bbox
+ center = (bbox[0][0] + bbox[1][0]) // 2, (bbox[0][1] + bbox[1][1]) // 2
+ close = False
+ for e in clickable_list:
+ bbox = e.bbox
+ center_ = (bbox[0][0] + bbox[1][0]) // 2, (bbox[0][1] + bbox[1][1]) // 2
+ dist = (abs(center[0] - center_[0]) ** 2 + abs(center[1] - center_[1]) ** 2) ** 0.5
+ if dist <= extra_config.get("min_dist", 30):
+ close = True
+ break
+ if not close:
+ elem_list.append(elem)
+
+ screenshot_labeled_path = task_dir.joinpath(f"{round_count}_labeled.png")
+ draw_bbox_multi(screenshot_path, screenshot_labeled_path, elem_list)
+ img_base64 = encode_image(screenshot_labeled_path)
+
+ parse_template = screenshot_parse_with_grid_template if grid_on else screenshot_parse_template
+
+ if grid_on:
+ env.rows, env.cols = draw_grid(screenshot_path, task_dir / f"{round_count}_grid.png")
+
+ ui_doc = self._makeup_ui_document(elem_list, docs_dir)
+ context = parse_template.format(ui_document=ui_doc, task_description=task_desc, last_act=last_act)
+ node = await SCREENSHOT_PARSE_NODE.fill(context=context, llm=self.llm, images=[img_base64])
+
+ if "error" in node.content:
+ return AndroidActionOutput(action_state=RunState.FAIL)
+
+ prompt = node.compile(context=context, schema="json", mode="auto")
+ OpLogItem(step=round_count, prompt=prompt, image=str(screenshot_labeled_path), response=node.content)
+
+ op_param = screenshot_parse_extract(node.instruct_content.model_dump(), grid_on)
+ if op_param.param_state == RunState.FINISH:
+ logger.info(f"op_param: {op_param}")
+ return AndroidActionOutput(action_state=RunState.FINISH)
+ if op_param.param_state == RunState.FAIL:
+ return AndroidActionOutput(action_state=RunState.FAIL)
+
+ last_act = op_param.last_act
+ if isinstance(op_param, TapOpParam):
+ x, y = elem_bbox_to_xy(elem_list[op_param.area - 1].bbox)
+ action = EnvAction(action_type=EnvActionType.SYSTEM_TAP, coord=(x, y))
+ elif isinstance(op_param, TextOpParam):
+ action = EnvAction(action_type=EnvActionType.USER_INPUT, input_txt=op_param.input_str)
+ elif isinstance(op_param, LongPressOpParam):
+ x, y = elem_bbox_to_xy(elem_list[op_param.area - 1].bbox)
+ action = EnvAction(action_type=EnvActionType.USER_LONGPRESS, coord=(x, y))
+ elif isinstance(op_param, SwipeOpParam):
+ x, y = elem_bbox_to_xy(elem_list[op_param.area - 1].bbox)
+ action = EnvAction(
+ action_type=EnvActionType.USER_SWIPE, coord=(x, y), orient=op_param.swipe_orient, dist=op_param.dist
+ )
+ elif isinstance(op_param, GridOpParam):
+ grid_on = True
+ elif isinstance(op_param, TapGridOpParam) or isinstance(op_param, LongPressGridOpParam):
+ x, y = area_to_xy(op_param.area, op_param.subarea, env.width, env.height, env.rows, env.cols)
+ if isinstance(op_param, TapGridOpParam):
+ action = EnvAction(action_type=EnvActionType.SYSTEM_TAP, coord=(x, y))
+ else:
+ # LongPressGridOpParam
+ action = EnvAction(action_type=EnvActionType.USER_LONGPRESS, coord=(x, y))
+ elif isinstance(op_param, SwipeGridOpParam):
+ start_x, start_y = area_to_xy(
+ op_param.start_area, op_param.start_subarea, env.width, env.height, env.rows, env.cols
+ )
+ end_x, end_y = area_to_xy(
+ op_param.end_area, op_param.end_subarea, env.width, env.height, env.rows, env.cols
+ )
+ action = EnvAction(
+ action_type=EnvActionType.USER_SWIPE_TO, coord=(start_x, start_y), tgt_coord=(end_x, end_y)
+ )
+
+ if not grid_on:
+ obs, _, _, _, info = env.step(action)
+ action_res = info["res"]
+ if action_res == ADB_EXEC_FAIL:
+ return AndroidActionOutput(action_state=RunState.FAIL)
+
+ if op_param.act_name != "grid":
+ grid_on = False
+
+ return AndroidActionOutput(data={"grid_on": grid_on, "last_act": last_act})
diff --git a/metagpt/ext/android_assistant/actions/screenshot_parse_an.py b/metagpt/ext/android_assistant/actions/screenshot_parse_an.py
new file mode 100644
index 000000000..eb23ba934
--- /dev/null
+++ b/metagpt/ext/android_assistant/actions/screenshot_parse_an.py
@@ -0,0 +1,48 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# @Desc : the ActionNode to parse screenshot
+
+from metagpt.actions.action_node import ActionNode
+
+OBSERVATION = ActionNode(
+ key="Observation", expected_type=str, instruction="Describe what you observe in the image", example=""
+)
+
+THOUGHT = ActionNode(
+ key="Thought",
+ expected_type=str,
+ instruction="To complete the given task, what is the next step I should do",
+ example="",
+)
+
+ACTION = ActionNode(
+ key="Action",
+ expected_type=str,
+ instruction="The function call with the correct parameters to proceed with the task. If you believe the task is "
+ "completed or there is nothing to be done, you should output FINISH. You cannot output anything else "
+ "except a function call or FINISH in this field.",
+ example="",
+)
+
+SUMMARY = ActionNode(
+ key="Summary",
+ expected_type=str,
+ instruction="Summarize your past actions along with your latest action in one or two sentences. Do not include "
+ "the numeric tag in your summary",
+ example="",
+)
+
+SUMMARY_GRID = ActionNode(
+ key="Summary",
+ expected_type=str,
+ instruction="Summarize your past actions along with your latest action in one or two sentences. Do not include "
+ "the grid area number in your summary",
+ example="",
+)
+
+NODES = [OBSERVATION, THOUGHT, ACTION, SUMMARY]
+
+NODES_GRID = [OBSERVATION, THOUGHT, ACTION, SUMMARY_GRID]
+
+SCREENSHOT_PARSE_NODE = ActionNode.from_children("ScreenshotParse", NODES)
+SCREENSHOT_PARSE_GRID_NODE = ActionNode.from_children("ScreenshotParseGrid", NODES_GRID)
diff --git a/metagpt/ext/android_assistant/actions/self_learn_and_reflect.py b/metagpt/ext/android_assistant/actions/self_learn_and_reflect.py
new file mode 100644
index 000000000..5e9cfbb45
--- /dev/null
+++ b/metagpt/ext/android_assistant/actions/self_learn_and_reflect.py
@@ -0,0 +1,231 @@
+# !/usr/bin/env python
+# -*- coding: utf-8 -*-
+# @Desc : LIKE scripts/self_explorer.py in stage=learn & mode=auto self_explore_task stage
+
+import ast
+from pathlib import Path
+
+from metagpt.actions.action import Action
+from metagpt.config2 import config
+from metagpt.environment.android.android_env import AndroidEnv
+from metagpt.environment.android.const import ADB_EXEC_FAIL
+from metagpt.environment.android.env_space import (
+ EnvAction,
+ EnvActionType,
+ EnvObsParams,
+ EnvObsType,
+)
+from metagpt.ext.android_assistant.actions.screenshot_parse_an import (
+ SCREENSHOT_PARSE_NODE,
+)
+from metagpt.ext.android_assistant.actions.self_learn_reflect_an import (
+ SELF_LEARN_REFLECT_NODE,
+)
+from metagpt.ext.android_assistant.prompts.assistant_prompt import (
+ screenshot_parse_self_explore_reflect_template as reflect_template,
+)
+from metagpt.ext.android_assistant.prompts.assistant_prompt import (
+ screenshot_parse_self_explore_template,
+)
+from metagpt.ext.android_assistant.utils.schema import (
+ ActionOp,
+ AndroidActionOutput,
+ AndroidElement,
+ Decision,
+ DocContent,
+ LongPressOpParam,
+ OpLogItem,
+ ReflectLogItem,
+ RunState,
+ SwipeOp,
+ SwipeOpParam,
+ TapOpParam,
+ TextOpParam,
+)
+from metagpt.ext.android_assistant.utils.utils import (
+ draw_bbox_multi,
+ elem_bbox_to_xy,
+ elem_list_from_xml_tree,
+ reflect_parse_extarct,
+ screenshot_parse_extract,
+)
+from metagpt.logs import logger
+from metagpt.utils.common import encode_image
+
+
+class SelfLearnAndReflect(Action):
+ name: str = "SelfLearnAndReflect"
+
+ useless_list: list[str] = [] # store useless elements uid
+
+ screenshot_before_path: str = ""
+ screenshot_before_base64: str = ""
+ elem_list: list[AndroidElement] = []
+ swipe_orient: str = "up"
+ act_name: str = ""
+ ui_area: int = -1
+
+ async def run(
+ self, round_count: int, task_desc: str, last_act: str, task_dir: Path, docs_dir: Path, env: AndroidEnv
+ ) -> AndroidActionOutput:
+ for path in [task_dir, docs_dir]:
+ path.mkdir(parents=True, exist_ok=True)
+ resp = await self.run_self_learn(round_count, task_desc, last_act, task_dir, env)
+ if resp.action_state != RunState.SUCCESS:
+ return resp
+
+ resp = await self.run_reflect(round_count, task_desc, last_act, task_dir, docs_dir, env)
+ return resp
+
+ async def run_self_learn(
+ self, round_count: int, task_desc: str, last_act: str, task_dir: Path, env: AndroidEnv
+ ) -> AndroidActionOutput:
+ extra_config = config.extra
+ screenshot_path: Path = env.observe(
+ EnvObsParams(obs_type=EnvObsType.GET_SCREENSHOT, ss_name=f"{round_count}_before", local_save_dir=task_dir)
+ )
+ xml_path: Path = env.observe(
+ EnvObsParams(obs_type=EnvObsType.GET_XML, xml_name=f"{round_count}", local_save_dir=task_dir)
+ )
+ if not screenshot_path.exists() or not xml_path.exists():
+ return AndroidActionOutput(action_state=RunState.FAIL)
+
+ elem_list = elem_list_from_xml_tree(xml_path, self.useless_list, extra_config.get("min_dist", 30))
+
+ screenshot_before_labeled_path = task_dir.joinpath(f"{round_count}_before_labeled.png")
+ draw_bbox_multi(screenshot_path, screenshot_before_labeled_path, elem_list)
+ img_base64 = encode_image(screenshot_before_labeled_path)
+ self.screenshot_before_base64 = img_base64
+ self.screenshot_before_path = screenshot_before_labeled_path
+
+ self_explore_template = screenshot_parse_self_explore_template
+ context = self_explore_template.format(task_description=task_desc, last_act=last_act)
+
+ node = await SCREENSHOT_PARSE_NODE.fill(context=context, llm=self.llm, images=[img_base64])
+ logger.debug(f"fill result:{node}")
+ if "error" in node.content:
+ return AndroidActionOutput(action_state=RunState.FAIL)
+ prompt = node.compile(context=context, schema="json", mode="auto")
+ # Modify WindowsPath to Str
+ OpLogItem(step=round_count, prompt=prompt, image=str(screenshot_before_labeled_path), response=node.content)
+ op_param = screenshot_parse_extract(node.instruct_content.model_dump(), grid_on=False)
+ # TODO Modify Op_param. When op_param.action is FINISH, how to solve this ?
+ if op_param.param_state == RunState.FINISH:
+ return AndroidActionOutput(action_state=RunState.FINISH)
+ if op_param.param_state == RunState.FAIL:
+ return AndroidActionOutput(action_state=RunState.FAIL)
+
+ if isinstance(op_param, TapOpParam):
+ self.ui_area = op_param.area
+ x, y = elem_bbox_to_xy(elem_list[op_param.area - 1].bbox)
+ action = EnvAction(action_type=EnvActionType.SYSTEM_TAP, coord=(x, y))
+ elif isinstance(op_param, TextOpParam):
+ action = EnvAction(action_type=EnvActionType.USER_INPUT, input_txt=op_param.input_str)
+ elif isinstance(op_param, LongPressOpParam):
+ self.ui_area = op_param.area
+ x, y = elem_bbox_to_xy(elem_list[op_param.area - 1].bbox)
+ action = EnvAction(action_type=EnvActionType.USER_LONGPRESS, coord=(x, y))
+ elif isinstance(op_param, SwipeOpParam):
+ self.ui_area = op_param.area
+ self.swipe_orient = op_param.swipe_orient
+ x, y = elem_bbox_to_xy(elem_list[op_param.area - 1].bbox)
+ action = EnvAction(
+ action_type=EnvActionType.USER_SWIPE, coord=(x, y), orient=op_param.swipe_orient, dist=op_param.dist
+ )
+
+ obs, _, _, _, info = env.step(action)
+ action_res = info["res"]
+ if action_res == ADB_EXEC_FAIL:
+ return AndroidActionOutput(action_state=RunState.FAIL)
+
+ self.elem_list = elem_list
+ self.act_name = op_param.act_name
+ return AndroidActionOutput()
+
+ async def run_reflect(
+ self, round_count: int, task_desc: str, last_act: str, task_dir: Path, docs_dir: Path, env: AndroidEnv
+ ) -> AndroidActionOutput:
+ screenshot_path: Path = env.observe(
+ EnvObsParams(obs_type=EnvObsType.GET_SCREENSHOT, ss_name=f"{round_count}_after", local_save_dir=task_dir)
+ )
+ if not screenshot_path.exists():
+ return AndroidActionOutput(action_state=RunState.FAIL)
+
+ screenshot_after_labeled_path = task_dir.joinpath(f"{round_count}_after_labeled.png")
+ draw_bbox_multi(screenshot_path, screenshot_after_labeled_path, elem_list=self.elem_list)
+ img_base64 = encode_image(screenshot_after_labeled_path)
+ if self.act_name == ActionOp.TAP.value:
+ action = "tapping"
+ elif self.act_name == ActionOp.LONG_PRESS.value:
+ action = "long pressing"
+ elif self.act_name == ActionOp.SWIPE.value:
+ action = "swiping"
+ if self.swipe_orient == SwipeOp.UP.value or self.swipe_orient == SwipeOp.DOWN.value:
+ action = "v_swipe"
+ elif self.swipe_orient == SwipeOp.LEFT.value or self.swipe_orient == SwipeOp.RIGHT.value:
+ action = "h_swipe"
+ else:
+ # TODO Test for assignment, This error is eupiped with the next.
+ logger.warning(f"Current action name parse failed, it's `{self.act_name}`")
+ action = None
+ context = reflect_template.format(
+ action=action, ui_element=str(self.ui_area), task_desc=task_desc, last_act=last_act
+ )
+ node = await SELF_LEARN_REFLECT_NODE.fill(
+ context=context, llm=self.llm, images=[self.screenshot_before_base64, img_base64]
+ )
+
+ if "error" in node.content:
+ return AndroidActionOutput(action_state=RunState.FAIL)
+
+ prompt = node.compile(context=context, schema="json", mode="auto")
+ ReflectLogItem(
+ step=round_count,
+ prompt=prompt,
+ image_before=str(self.screenshot_before_path),
+ image_after=str(screenshot_after_labeled_path),
+ response=node.content,
+ )
+
+ op_param = reflect_parse_extarct(node.instruct_content.model_dump())
+ if op_param.param_state == RunState.FINISH:
+ return AndroidActionOutput(action_state=RunState.FINISH)
+ if op_param.param_state == RunState.FAIL:
+ return AndroidActionOutput(action_state=RunState.FAIL)
+
+ logger.info(
+ f"reflect_parse_extarct decision: {op_param.decision}, "
+ f"elem_list size: {len(self.elem_list)}, ui_area: {self.ui_area}"
+ )
+ # TODO here will cause `IndexError: list index out of range`.
+ # Maybe you should clink back to the desktop in the simulator
+ resource_id = self.elem_list[int(self.ui_area) - 1].uid
+ if op_param.decision == Decision.INEFFECTIVE.value:
+ self.useless_list.append(resource_id)
+ last_act = "NONE" # TODO global
+ elif op_param.decision in [Decision.BACK.value, Decision.CONTINUE.value, Decision.SUCCESS.value]:
+ if op_param.decision in [Decision.BACK.value, Decision.CONTINUE.value]:
+ self.useless_list.append(resource_id)
+ last_act = "NONE"
+ if op_param.decision == Decision.BACK.value:
+ action = EnvAction(action_type=EnvActionType.SYSTEM_BACK)
+ obs, _, _, _, info = env.step(action)
+ if info["res"] == ADB_EXEC_FAIL:
+ return AndroidActionOutput(action_state=RunState.FAIL)
+ doc = op_param.documentation
+ doc_path = docs_dir.joinpath(f"{resource_id}.txt")
+ if doc_path.exists():
+ try:
+ doc_content = ast.literal_eval(doc_path.read_text())
+ except Exception as exp:
+ logger.error(f"ast parse doc: {doc_path} failed, exp: {exp}")
+ return AndroidActionOutput(action_state=RunState.FAIL)
+
+ if doc_content[self.act_name]:
+ logger.info(f"Documentation for the element {resource_id} already exists.")
+ return AndroidActionOutput(action_state=RunState.FAIL)
+ else:
+ doc_content = DocContent()
+ setattr(doc_content, self.act_name, doc)
+ doc_path.write_text(str(doc_content))
+ return AndroidActionOutput(data={"last_act": last_act})
diff --git a/metagpt/ext/android_assistant/actions/self_learn_reflect_an.py b/metagpt/ext/android_assistant/actions/self_learn_reflect_an.py
new file mode 100644
index 000000000..305b7376a
--- /dev/null
+++ b/metagpt/ext/android_assistant/actions/self_learn_reflect_an.py
@@ -0,0 +1,21 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# @Desc : the ActionNode to parse Reflection
+
+from metagpt.actions.action_node import ActionNode
+
+DECISION = ActionNode(
+ key="Decision", expected_type=str, instruction="explain why you made this decision", example="BACK"
+)
+
+
+THOUGHT = ActionNode(key="Thought", expected_type=str, instruction="explain why you made this decision", example="")
+
+
+DOCUMENTATION = ActionNode(
+ key="Documentation", expected_type=str, instruction="describe the function of the UI element", example=""
+)
+
+
+NODES = [DECISION, THOUGHT, DOCUMENTATION]
+SELF_LEARN_REFLECT_NODE = ActionNode.from_children("SelfLearnReflect", NODES)
diff --git a/metagpt/ext/android_assistant/prompts/__init__.py b/metagpt/ext/android_assistant/prompts/__init__.py
new file mode 100644
index 000000000..2bcf8efd0
--- /dev/null
+++ b/metagpt/ext/android_assistant/prompts/__init__.py
@@ -0,0 +1,3 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# @Desc :
diff --git a/metagpt/ext/android_assistant/prompts/assistant_prompt.py b/metagpt/ext/android_assistant/prompts/assistant_prompt.py
new file mode 100644
index 000000000..34baf5841
--- /dev/null
+++ b/metagpt/ext/android_assistant/prompts/assistant_prompt.py
@@ -0,0 +1,168 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# @Desc : the prompt templates of assistant learning and acting
+
+screenshot_parse_template = """You are an agent that is trained to perform some basic tasks on a smartphone. You will be given a
+smartphone screenshot. The interactive UI elements on the screenshot are labeled with numeric tags starting from 1. The
+numeric tag of each interactive element is located in the center of the element.
+
+You can call the following functions to control the smartphone:
+
+1. tap(element: int)
+This function is used to tap an UI element shown on the smartphone screen.
+"element" is a numeric tag assigned to an UI element shown on the smartphone screen.
+A simple use case can be tap(5), which taps the UI element labeled with the number 5.
+
+2. text(text_input: str)
+This function is used to insert text input in an input field/box. text_input is the string you want to insert and must
+be wrapped with double quotation marks. A simple use case can be text("Hello, world!"), which inserts the string
+"Hello, world!" into the input area on the smartphone screen. This function is usually callable when you see a keyboard
+showing in the lower half of the screen.
+
+3. long_press(element: int)
+This function is used to long press an UI element shown on the smartphone screen.
+"element" is a numeric tag assigned to an UI element shown on the smartphone screen.
+A simple use case can be long_press(5), which long presses the UI element labeled with the number 5.
+
+4. swipe(element: int, direction: str, dist: str)
+This function is used to swipe an UI element shown on the smartphone screen, usually a scroll view or a slide bar.
+"element" is a numeric tag assigned to an UI element shown on the smartphone screen. "direction" is a string that
+represents one of the four directions: up, down, left, right. "direction" must be wrapped with double quotation
+marks. "dist" determines the distance of the swipe and can be one of the three options: short, medium, long. You should
+choose the appropriate distance option according to your need.
+A simple use case can be swipe(21, "up", "medium"), which swipes up the UI element labeled with the number 21 for a
+medium distance.
+
+5. grid()
+You should call this function when you find the element you want to interact with is not labeled with a numeric tag and
+other elements with numeric tags cannot help with the task. The function will bring up a grid overlay to divide the
+smartphone screen into small areas and this will give you more freedom to choose any part of the screen to tap, long
+press, or swipe.
+{ui_document}
+The task you need to complete is to: {task_description}. Your past actions to proceed with this task are summarized as
+follows: {last_act}
+Now, given the documentation and the following labeled screenshot, you need to think and call the function needed to
+proceed with the task. Your output should include three parts in the given format:
+
+You can only take one action at a time, so please directly call the function."""
+
+screenshot_parse_with_grid_template = """You are an agent that is trained to perform some basic tasks on a smartphone. You will be given
+a smartphone screenshot overlaid by a grid. The grid divides the screenshot into small square areas. Each area is
+labeled with an integer in the top-left corner.
+
+You can call the following functions to control the smartphone:
+
+1. tap(area: int, subarea: str)
+This function is used to tap a grid area shown on the smartphone screen. "area" is the integer label assigned to a grid
+area shown on the smartphone screen. "subarea" is a string representing the exact location to tap within the grid area.
+It can take one of the nine values: center, top-left, top, top-right, left, right, bottom-left, bottom, and
+bottom-right.
+A simple use case can be tap(5, "center"), which taps the exact center of the grid area labeled with the number 5.
+
+2. long_press(area: int, subarea: str)
+This function is used to long press a grid area shown on the smartphone screen. "area" is the integer label assigned to
+a grid area shown on the smartphone screen. "subarea" is a string representing the exact location to long press within
+the grid area. It can take one of the nine values: center, top-left, top, top-right, left, right, bottom-left, bottom,
+and bottom-right.
+A simple use case can be long_press(7, "top-left"), which long presses the top left part of the grid area labeled with
+the number 7.
+
+3. swipe(start_area: int, start_subarea: str, end_area: int, end_subarea: str)
+This function is used to perform a swipe action on the smartphone screen, especially when you want to interact with a
+scroll view or a slide bar. "start_area" is the integer label assigned to the grid area which marks the starting
+location of the swipe. "start_subarea" is a string representing the exact location to begin the swipe within the grid
+area. "end_area" is the integer label assigned to the grid area which marks the ending location of the swipe.
+"end_subarea" is a string representing the exact location to end the swipe within the grid area.
+The two subarea parameters can take one of the nine values: center, top-left, top, top-right, left, right, bottom-left,
+bottom, and bottom-right.
+A simple use case can be swipe(21, "center", 25, "right"), which performs a swipe starting from the center of grid area
+21 to the right part of grid area 25.
+
+The task you need to complete is to: {task_description}. Your past actions to proceed with this task are summarized as
+follows: {last_act}
+Now, given the following labeled screenshot, you need to think and call the function needed to proceed with the task.
+Your output should include three parts in the given format:
+
+You can only take one action at a time, so please directly call the function."""
+
+screenshot_parse_self_explore_template = """You are an agent that is trained to complete certain tasks on a smartphone. You will be
+given a screenshot of a smartphone app. The interactive UI elements on the screenshot are labeled with numeric tags
+starting from 1.
+
+You can call the following functions to interact with those labeled elements to control the smartphone:
+
+1. tap(element: int)
+This function is used to tap an UI element shown on the smartphone screen.
+"element" is a numeric tag assigned to an UI element shown on the smartphone screen.
+A simple use case can be tap(5), which taps the UI element labeled with the number 5.
+
+2. text(text_input: str)
+This function is used to insert text input in an input field/box. text_input is the string you want to insert and must
+be wrapped with double quotation marks. A simple use case can be text("Hello, world!"), which inserts the string
+"Hello, world!" into the input area on the smartphone screen. This function is only callable when you see a keyboard
+showing in the lower half of the screen.
+
+3. long_press(element: int)
+This function is used to long press an UI element shown on the smartphone screen.
+"element" is a numeric tag assigned to an UI element shown on the smartphone screen.
+A simple use case can be long_press(5), which long presses the UI element labeled with the number 5.
+
+4. swipe(element: int, direction: str, dist: str)
+This function is used to swipe an UI element shown on the smartphone screen, usually a scroll view or a slide bar.
+"element" is a numeric tag assigned to an UI element shown on the smartphone screen. "direction" is a string that
+represents one of the four directions: up, down, left, right. "direction" must be wrapped with double quotation
+marks. "dist" determines the distance of the swipe and can be one of the three options: short, medium, long. You should
+choose the appropriate distance option according to your need.
+A simple use case can be swipe(21, "up", "medium"), which swipes up the UI element labeled with the number 21 for a
+medium distance.
+
+The task you need to complete is to {task_description}. Your past actions to proceed with this task are summarized as
+follows: {last_act}
+Now, given the following labeled screenshot, you need to think and call the function needed to proceed with the task.
+Your output should include three parts in the given format:
+
+You can only take one action at a time, so please directly call the function."""
+
+screenshot_parse_self_explore_reflect_template = """I will give you screenshots of a mobile app before and after {action} the UI
+element labeled with the number '{ui_element}' on the first screenshot. The numeric tag of each element is located at
+the center of the element. The action of {action} this UI element was described as follows:
+{last_act}
+The action was also an attempt to proceed with a larger task, which is to {task_desc}. Your job is to carefully analyze
+the difference between the two screenshots to determine if the action is in accord with the description above and at
+the same time effectively moved the task forward. Your output should be determined based on the following situations:
+1. BACK
+If you think the action navigated you to a page where you cannot proceed with the given task, you should go back to the
+previous interface. At the same time, describe the functionality of the UI element concisely in one or two sentences by
+observing the difference between the two screenshots. Notice that your description of the UI element should focus on
+the general function. Never include the numeric tag of the UI element in your description. You can use pronouns such as
+"the UI element" to refer to the element. Your output should be in the following format:
+Decision: BACK
+Thought:
+Documentation:
+2. INEFFECTIVE
+If you find the action changed nothing on the screen (screenshots before and after the action are identical), you
+should continue to interact with other elements on the screen. Notice that if you find the location of the cursor
+changed between the two screenshots, then they are not identical. Your output should be in the following format:
+Decision: INEFFECTIVE
+Thought:
+Documentation:
+3. CONTINUE
+If you find the action changed something on the screen but does not reflect the action description above and did not
+move the given task forward, you should continue to interact with other elements on the screen. At the same time,
+describe the functionality of the UI element concisely in one or two sentences by observing the difference between the
+two screenshots. Notice that your description of the UI element should focus on the general function. Never include the
+numeric tag of the UI element in your description. You can use pronouns such as "the UI element" to refer to the
+element. Your output should be in the following format:
+Decision: CONTINUE
+Thought:
+Documentation:
+4. SUCCESS
+If you think the action successfully moved the task forward (even though it did not completed the task), you should
+describe the functionality of the UI element concisely in one or two sentences. Notice that your description of the UI
+element should focus on the general function. Never include the numeric tag of the UI element in your description. You
+can use pronouns such as "the UI element" to refer to the element. Your output should be in the following format:
+Decision: SUCCESS
+Thought:
+Documentation:
+"""
diff --git a/metagpt/ext/android_assistant/prompts/operation_prompt.py b/metagpt/ext/android_assistant/prompts/operation_prompt.py
new file mode 100644
index 000000000..1bde53f04
--- /dev/null
+++ b/metagpt/ext/android_assistant/prompts/operation_prompt.py
@@ -0,0 +1,45 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# @Desc : the prompt templates of phone operation
+
+tap_doc_template = """I will give you the screenshot of a mobile app before and after tapping the UI element labeled
+with the number {ui_element} on the screen. The numeric tag of each element is located at the center of the element.
+Tapping this UI element is a necessary part of proceeding with a larger task, which is to . Your task is to
+describe the functionality of the UI element concisely in one or two sentences. Notice that your description of the UI
+element should focus on the general function. For example, if the UI element is used to navigate to the chat window
+with John, your description should not include the name of the specific person. Just say: "Tapping this area will
+navigate the user to the chat window". Never include the numeric tag of the UI element in your description. You can use
+pronouns such as "the UI element" to refer to the element."""
+
+text_doc_template = """I will give you the screenshot of a mobile app before and after typing in the input area labeled
+with the number {ui_element} on the screen. The numeric tag of each element is located at the center of the element.
+Typing in this UI element is a necessary part of proceeding with a larger task, which is to . Your task is
+to describe the functionality of the UI element concisely in one or two sentences. Notice that your description of the
+UI element should focus on the general function. For example, if the change of the screenshot shows that the user typed
+"How are you?" in the chat box, you do not need to mention the actual text. Just say: "This input area is used for the
+user to type a message to send to the chat window.". Never include the numeric tag of the UI element in your
+description. You can use pronouns such as "the UI element" to refer to the element."""
+
+long_press_doc_template = """I will give you the screenshot of a mobile app before and after long pressing the UI
+element labeled with the number {ui_element} on the screen. The numeric tag of each element is located at the center of
+the element. Long pressing this UI element is a necessary part of proceeding with a larger task, which is to
+. Your task is to describe the functionality of the UI element concisely in one or two sentences. Notice
+that your description of the UI element should focus on the general function. For example, if long pressing the UI
+element redirects the user to the chat window with John, your description should not include the name of the specific
+person. Just say: "Long pressing this area will redirect the user to the chat window". Never include the numeric tag of
+the UI element in your description. You can use pronouns such as "the UI element" to refer to the element."""
+
+swipe_doc_template = """I will give you the screenshot of a mobile app before and after swiping the UI
+element labeled with the number {ui_element} on the screen. The numeric tag of each element is located at the center of
+the element. Swiping this UI element is a necessary part of proceeding with a larger task, which is to .
+Your task is to describe the functionality of the UI element concisely in one or two sentences. Notice that your
+description of the UI element should be as general as possible. For example, if swiping the UI element increases the
+contrast ratio of an image of a building, your description should be just like this: "Swiping this area enables the
+user to tune a specific parameter of the image". Never include the numeric tag of the UI element in your description.
+You can use pronouns such as "the UI element" to refer to the element."""
+
+refine_doc_suffix = """\nA documentation of this UI element generated from previous demos is shown below. Your
+generated description should be based on this previous doc and optimize it. Notice that it is possible that your
+understanding of the function of the UI element derived from the given screenshots conflicts with the previous doc,
+because the function of a UI element can be flexible. In this case, your generated description should combine both.
+Old documentation of this UI element: {old_doc}"""
diff --git a/metagpt/ext/android_assistant/roles/__init__.py b/metagpt/ext/android_assistant/roles/__init__.py
new file mode 100644
index 000000000..2bcf8efd0
--- /dev/null
+++ b/metagpt/ext/android_assistant/roles/__init__.py
@@ -0,0 +1,3 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# @Desc :
diff --git a/metagpt/ext/android_assistant/roles/android_assistant.py b/metagpt/ext/android_assistant/roles/android_assistant.py
new file mode 100644
index 000000000..45636f519
--- /dev/null
+++ b/metagpt/ext/android_assistant/roles/android_assistant.py
@@ -0,0 +1,146 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# @Desc : android assistant to learn from app operations and operate apps
+import time
+from datetime import datetime
+from pathlib import Path
+from typing import Optional
+
+from pydantic import Field
+
+from metagpt.actions.add_requirement import UserRequirement
+from metagpt.config2 import config
+from metagpt.const import EXAMPLE_PATH
+from metagpt.ext.android_assistant.actions.manual_record import ManualRecord
+from metagpt.ext.android_assistant.actions.parse_record import ParseRecord
+from metagpt.ext.android_assistant.actions.screenshot_parse import ScreenshotParse
+from metagpt.ext.android_assistant.actions.self_learn_and_reflect import (
+ SelfLearnAndReflect,
+)
+from metagpt.ext.android_assistant.utils.schema import AndroidActionOutput, RunState
+from metagpt.logs import logger
+from metagpt.roles.role import Role, RoleReactMode
+from metagpt.schema import Message
+
+
+class AndroidAssistant(Role):
+ name: str = "Nick"
+ profile: str = "AndroidAssistant"
+ goal: str = "operate the mobile phone's apps with self-learn"
+
+ task_desc: str = ""
+ round_count: int = 0
+ last_act: str = "None"
+ output_root_dir: Optional[Path] = Field(default=None)
+ task_dir: Optional[Path] = Field(default=None)
+ docs_dir: Optional[Path] = Field(default=None)
+ grid_on: bool = Field(default=False)
+
+ def __init__(self, **data):
+ super().__init__(**data)
+
+ self._watch([UserRequirement, AndroidActionOutput])
+ extra_config = config.extra
+ self.task_desc = extra_config.get("task_desc", "Just explore any app in this phone!")
+ app_name = extra_config.get("app_name", "demo")
+ data_dir = self.output_root_dir.absolute().joinpath("output") or EXAMPLE_PATH.joinpath(
+ "android_assistant/output"
+ )
+ cur_datetime = datetime.fromtimestamp(int(time.time())).strftime("%Y-%m-%d_%H-%M-%S")
+
+ """Firstly, we decide the state with user config, further, we can do it automatically, like if it's new app,
+ run the learn first and then do the act stage or learn it during the action.
+ """
+ stage = extra_config.get("stage")
+ mode = extra_config.get("mode")
+ if stage == "learn" and mode == "manual":
+ # choose ManualRecord and then run ParseRecord
+ # Remember, only run each action only one time, no need to run n_round.
+ self.set_actions([ManualRecord, ParseRecord])
+ self.task_dir = data_dir.joinpath(app_name, f"manual_learn_{cur_datetime}")
+ self.docs_dir = data_dir.joinpath(app_name, "manual_docs")
+ elif stage == "learn" and mode == "auto":
+ # choose SelfLearnAndReflect to run
+ self.set_actions([SelfLearnAndReflect])
+ self.task_dir = data_dir.joinpath(app_name, f"auto_learn_{cur_datetime}")
+ self.docs_dir = data_dir.joinpath(app_name, "auto_docs")
+ elif stage == "act":
+ # choose ScreenshotParse to run
+ self.set_actions([ScreenshotParse])
+ self.task_dir = data_dir.joinpath(app_name, f"act_{cur_datetime}")
+ if mode == "manual":
+ self.docs_dir = data_dir.joinpath(app_name, "manual_docs")
+ else:
+ self.docs_dir = data_dir.joinpath(app_name, "auto_docs")
+ else:
+ raise ValueError(f"invalid stage: {stage}, mode: {mode}")
+
+ self._check_dir()
+
+ self._set_react_mode(RoleReactMode.BY_ORDER)
+
+ def _check_dir(self):
+ self.task_dir.mkdir(parents=True, exist_ok=True)
+ self.docs_dir.mkdir(parents=True, exist_ok=True)
+
+ async def react(self) -> Message:
+ self.round_count += 1
+ result = await super().react()
+ logger.debug(f"react result {result}")
+ return result
+
+ async def _observe(self, ignore_memory=True) -> int:
+ """ignore old memory to make it run multi rounds inside a role"""
+ newest_msgs = self.rc.memory.get(k=1)
+ newest_msg = newest_msgs[0] if newest_msgs else None
+ if newest_msg and (RunState.SUCCESS.value.upper() not in newest_msg.content):
+ ignore_memory = False
+ state_val = newest_msg.content.split(".")[-1] # RoundCount: 1, action_state: RunState.SUCCESS
+ logger.warning(f"Latest action_state is {state_val}, will run in the remainder rounds without `react`")
+ return await super()._observe(ignore_memory)
+
+ async def _act(self) -> Message:
+ logger.info(f"{self._setting}: to do {self.rc.todo}({self.rc.todo.name})")
+ todo = self.rc.todo
+ if isinstance(todo, ManualRecord):
+ resp = await todo.run(task_dir=self.task_dir, task_desc=self.task_desc, env=self.rc.env)
+ elif isinstance(todo, ParseRecord):
+ resp = await todo.run(
+ task_dir=self.task_dir,
+ docs_dir=self.docs_dir,
+ )
+ elif isinstance(todo, SelfLearnAndReflect):
+ resp = await todo.run(
+ round_count=self.round_count,
+ task_desc=self.task_desc,
+ last_act=self.last_act,
+ task_dir=self.task_dir,
+ docs_dir=self.docs_dir,
+ env=self.rc.env,
+ )
+ if resp.action_state == RunState.SUCCESS:
+ self.last_act = resp.data.get("last_act")
+ elif isinstance(todo, ScreenshotParse):
+ resp = await todo.run(
+ round_count=self.round_count,
+ task_desc=self.task_desc,
+ last_act=self.last_act,
+ task_dir=self.task_dir,
+ docs_dir=self.docs_dir,
+ grid_on=self.grid_on,
+ env=self.rc.env,
+ )
+ if resp.action_state == RunState.SUCCESS:
+ logger.info(f"grid_on: {resp.data.get('grid_on')}")
+ self.grid_on = resp.data.get("grid_on", False)
+ self.last_act = resp.data.get("last_act", "None")
+ msg = Message(
+ content=f"RoundCount: {self.round_count}, action_state: {resp.action_state}",
+ role=self.profile,
+ cause_by=type(resp),
+ send_from=self.name,
+ send_to=self.name,
+ )
+
+ self.rc.memory.add(msg)
+ return msg
diff --git a/metagpt/ext/android_assistant/utils/__init__.py b/metagpt/ext/android_assistant/utils/__init__.py
new file mode 100644
index 000000000..2bcf8efd0
--- /dev/null
+++ b/metagpt/ext/android_assistant/utils/__init__.py
@@ -0,0 +1,3 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# @Desc :
diff --git a/metagpt/ext/android_assistant/utils/schema.py b/metagpt/ext/android_assistant/utils/schema.py
new file mode 100644
index 000000000..c066f98b6
--- /dev/null
+++ b/metagpt/ext/android_assistant/utils/schema.py
@@ -0,0 +1,158 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# @Desc :
+
+from enum import Enum
+
+from pydantic import BaseModel, Field, field_validator
+
+
+class ActionOp(Enum):
+ TAP = "tap"
+ LONG_PRESS = "long_press"
+ TEXT = "text"
+ SWIPE = "swipe"
+ VERTICAL_SWIPE = "v_swipe"
+ HORIZONTAL_SWIPE = "h_swipe"
+ GRID = "grid"
+ STOP = "stop"
+
+
+class SwipeOp(Enum):
+ UP = "up"
+ DOWN = "down"
+ LEFT = "left"
+ RIGHT = "right"
+
+
+class Decision(Enum):
+ BACK = "BACK"
+ INEFFECTIVE = "INEFFECTIVE"
+ CONTINUE = "CONTINUE"
+ SUCCESS = "SUCCESS"
+
+ @classmethod
+ def values(cls):
+ return [item.value for item in cls]
+
+
+class AndroidElement(BaseModel):
+ """UI Element"""
+
+ uid: str = Field(default="")
+ bbox: tuple[tuple[int, int], tuple[int, int]] = Field(default={})
+ attrib: str = Field(default="")
+
+
+class OpLogItem(BaseModel):
+ """log content for self-learn or task act"""
+
+ step: int = Field(default=0)
+ prompt: str = Field(default="")
+ image: str = Field(default="")
+ response: str = Field(default="")
+
+
+class ReflectLogItem(BaseModel):
+ """log content for self-learn-reflect"""
+
+ step: int = Field(default=0)
+ prompt: str = Field(default="")
+ image_before: str = Field(default="")
+ image_after: str = Field(default="")
+ response: str = Field(default="")
+
+
+class RecordLogItem(BaseModel):
+ """log content for record parse, same as ReflectLogItem"""
+
+ step: int = Field(default=0)
+ prompt: str = Field(default="")
+ image_before: str = Field(default="")
+ image_after: str = Field(default="")
+ response: str = Field(default="")
+
+
+class DocContent(BaseModel):
+ tap: str = Field(default="")
+ text: str = Field(default="")
+ v_swipe: str = Field(default="")
+ h_swipe: str = Field(default="")
+ long_press: str = Field(default="")
+
+
+# start =================== define different Action Op and its params =============
+class RunState(Enum):
+ """run state"""
+
+ SUCCESS = "success"
+ FINISH = "finish"
+ FAIL = "fail"
+
+
+class BaseOpParam(BaseModel):
+ act_name: str = Field(default="", validate_default=True)
+ last_act: str = Field(default="None")
+ param_state: RunState = Field(default=RunState.SUCCESS, description="return state when extract params")
+
+
+class TapOpParam(BaseOpParam):
+ area: int = Field(default=-1)
+
+
+class TextOpParam(BaseOpParam):
+ input_str: str = Field(default="")
+
+
+class LongPressOpParam(BaseOpParam):
+ area: int = Field(default=-1)
+
+
+# Modify This SwipeOp to SwipeOpParam, Need better name
+class SwipeOpParam(BaseOpParam):
+ area: int = Field(default=-1)
+ swipe_orient: str = Field(default="up")
+ dist: str = Field(default="")
+
+
+class GridOpParam(BaseOpParam):
+ act_name: str = Field(default="")
+
+
+class BaseGridOpParam(BaseOpParam):
+ @field_validator("act_name", mode="before")
+ @classmethod
+ def check_act_name(cls, act_name: str) -> str:
+ return f"{act_name}_grid"
+
+
+class TapGridOpParam(BaseGridOpParam):
+ area: int = Field(default=-1)
+ subarea: str = Field(default="")
+
+
+class LongPressGridOpParam(BaseGridOpParam):
+ area: int = Field(default=-1)
+ subarea: str = Field(default="")
+
+
+class SwipeGridOpParam(BaseGridOpParam):
+ start_area: int = Field(default=-1)
+ start_subarea: str = Field(default="")
+ end_area: int = Field(default=-1)
+ end_subarea: str = Field(default="")
+
+
+# end =================== define different Action Op and its params =============
+
+
+class ReflectOp(BaseModel):
+ decision: str = ""
+ thought: str = ""
+ documentation: str = ""
+ param_state: RunState = RunState.SUCCESS
+
+
+class AndroidActionOutput(BaseModel):
+ data: dict = Field(default=dict())
+ action_state: RunState = Field(default=RunState.SUCCESS)
diff --git a/metagpt/ext/android_assistant/utils/utils.py b/metagpt/ext/android_assistant/utils/utils.py
new file mode 100644
index 000000000..f1fa13869
--- /dev/null
+++ b/metagpt/ext/android_assistant/utils/utils.py
@@ -0,0 +1,329 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# @Desc :
+
+import re
+from pathlib import Path
+from typing import Union
+from xml.etree.ElementTree import Element, iterparse
+
+import cv2
+import pyshine as ps
+
+from metagpt.config2 import config
+from metagpt.ext.android_assistant.utils.schema import (
+ ActionOp,
+ AndroidElement,
+ BaseGridOpParam,
+ BaseOpParam,
+ Decision,
+ GridOpParam,
+ LongPressGridOpParam,
+ LongPressOpParam,
+ ReflectOp,
+ RunState,
+ SwipeGridOpParam,
+ SwipeOpParam,
+ TapGridOpParam,
+ TapOpParam,
+ TextOpParam,
+)
+from metagpt.logs import logger
+
+
+def get_id_from_element(elem: Element) -> str:
+ bounds = elem.attrib["bounds"][1:-1].split("][")
+ x1, y1 = map(int, bounds[0].split(","))
+ x2, y2 = map(int, bounds[1].split(","))
+ elem_w, elem_h = x2 - x1, y2 - y1
+ if "resource-id" in elem.attrib and elem.attrib["resource-id"]:
+ elem_id = elem.attrib["resource-id"].replace(":", ".").replace("/", "_")
+ else:
+ elem_id = f"{elem.attrib['class']}_{elem_w}_{elem_h}"
+ if "content-desc" in elem.attrib and elem.attrib["content-desc"] and len(elem.attrib["content-desc"]) < 20:
+ content_desc = elem.attrib["content-desc"].replace("/", "_").replace(" ", "").replace(":", "_")
+ elem_id += f"_{content_desc}"
+ return elem_id
+
+
+def traverse_xml_tree(xml_path: Path, elem_list: list[AndroidElement], attrib: str, add_index=False):
+ path = []
+ extra_config = config.extra
+ for event, elem in iterparse(str(xml_path), ["start", "end"]):
+ if event == "start":
+ path.append(elem)
+ if attrib in elem.attrib and elem.attrib[attrib] == "true":
+ parent_prefix = ""
+ if len(path) > 1:
+ parent_prefix = get_id_from_element(path[-2])
+ bounds = elem.attrib["bounds"][1:-1].split("][")
+ x1, y1 = map(int, bounds[0].split(","))
+ x2, y2 = map(int, bounds[1].split(","))
+ center = (x1 + x2) // 2, (y1 + y2) // 2
+ elem_id = get_id_from_element(elem)
+ if parent_prefix:
+ elem_id = parent_prefix + "_" + elem_id
+ if add_index:
+ elem_id += f"_{elem.attrib['index']}"
+ close = False
+ for e in elem_list:
+ bbox = e.bbox
+ center_ = (bbox[0][0] + bbox[1][0]) // 2, (bbox[0][1] + bbox[1][1]) // 2
+ dist = (abs(center[0] - center_[0]) ** 2 + abs(center[1] - center_[1]) ** 2) ** 0.5
+ if dist <= extra_config.get("min_dist", 30):
+ close = True
+ break
+ if not close:
+ elem_list.append(AndroidElement(uid=elem_id, bbox=((x1, y1), (x2, y2)), attrib=attrib))
+
+ if event == "end":
+ path.pop()
+
+
+def elem_list_from_xml_tree(xml_path: Path, useless_list: list[str], min_dist: int) -> list[AndroidElement]:
+ clickable_list = []
+ focusable_list = []
+ traverse_xml_tree(xml_path, clickable_list, "clickable", True)
+ traverse_xml_tree(xml_path, focusable_list, "focusable", True)
+ elem_list = []
+ for elem in clickable_list:
+ if elem.uid in useless_list:
+ continue
+ elem_list.append(elem)
+ for elem in focusable_list:
+ if elem.uid in useless_list:
+ continue
+ bbox = elem.bbox
+ center = (bbox[0][0] + bbox[1][0]) // 2, (bbox[0][1] + bbox[1][1]) // 2
+ close = False
+ for e in clickable_list:
+ bbox = e.bbox
+ center_ = (bbox[0][0] + bbox[1][0]) // 2, (bbox[0][1] + bbox[1][1]) // 2
+ dist = (abs(center[0] - center_[0]) ** 2 + abs(center[1] - center_[1]) ** 2) ** 0.5
+ if dist <= min_dist:
+ close = True
+ break
+ if not close:
+ elem_list.append(elem)
+ return elem_list
+
+
+def draw_bbox_multi(
+ img_path: Path,
+ output_path: Path,
+ elem_list: list[AndroidElement],
+ record_mode: bool = False,
+ dark_mode: bool = False,
+):
+ imgcv = cv2.imread(str(img_path))
+ count = 1
+ for elem in elem_list:
+ try:
+ top_left = elem.bbox[0]
+ bottom_right = elem.bbox[1]
+ left, top = top_left[0], top_left[1]
+ right, bottom = bottom_right[0], bottom_right[1]
+ label = str(count)
+ if record_mode:
+ if elem.attrib == "clickable":
+ color = (250, 0, 0)
+ elif elem.attrib == "focusable":
+ color = (0, 0, 250)
+ else:
+ color = (0, 250, 0)
+ imgcv = ps.putBText(
+ imgcv,
+ label,
+ text_offset_x=(left + right) // 2 + 10,
+ text_offset_y=(top + bottom) // 2 + 10,
+ vspace=10,
+ hspace=10,
+ font_scale=1,
+ thickness=2,
+ background_RGB=color,
+ text_RGB=(255, 250, 250),
+ alpha=0.5,
+ )
+ else:
+ text_color = (10, 10, 10) if dark_mode else (255, 250, 250)
+ bg_color = (255, 250, 250) if dark_mode else (10, 10, 10)
+ imgcv = ps.putBText(
+ imgcv,
+ label,
+ text_offset_x=(left + right) // 2 + 10,
+ text_offset_y=(top + bottom) // 2 + 10,
+ vspace=10,
+ hspace=10,
+ font_scale=1,
+ thickness=2,
+ background_RGB=bg_color,
+ text_RGB=text_color,
+ alpha=0.5,
+ )
+ except Exception as e:
+ logger.error(f"ERROR: An exception occurs while labeling the image\n{e}")
+ count += 1
+ cv2.imwrite(str(output_path), imgcv)
+ return imgcv
+
+
+def draw_grid(img_path: Path, output_path: Path) -> tuple[int, int]:
+ def get_unit_len(n):
+ for i in range(1, n + 1):
+ if n % i == 0 and 120 <= i <= 180:
+ return i
+ return -1
+
+ image = cv2.imread(str(img_path))
+ height, width, _ = image.shape
+ color = (255, 116, 113)
+ unit_height = get_unit_len(height)
+ if unit_height < 0:
+ unit_height = 120
+ unit_width = get_unit_len(width)
+ if unit_width < 0:
+ unit_width = 120
+ thick = int(unit_width // 50)
+ rows = height // unit_height
+ cols = width // unit_width
+ for i in range(rows):
+ for j in range(cols):
+ label = i * cols + j + 1
+ left = int(j * unit_width)
+ top = int(i * unit_height)
+ right = int((j + 1) * unit_width)
+ bottom = int((i + 1) * unit_height)
+ cv2.rectangle(image, (left, top), (right, bottom), color, thick // 2)
+ cv2.putText(
+ image,
+ str(label),
+ (left + int(unit_width * 0.05) + 3, top + int(unit_height * 0.3) + 3),
+ 0,
+ int(0.01 * unit_width),
+ (0, 0, 0),
+ thick,
+ )
+ cv2.putText(
+ image,
+ str(label),
+ (left + int(unit_width * 0.05), top + int(unit_height * 0.3)),
+ 0,
+ int(0.01 * unit_width),
+ color,
+ thick,
+ )
+ cv2.imwrite(str(output_path), image)
+ return rows, cols
+
+
+def area_to_xy(area: int, subarea: str, width: int, height: int, rows: int, cols: int) -> tuple[int, int]:
+ area -= 1
+ row, col = area // cols, area % cols
+ x_0, y_0 = col * (width // cols), row * (height // rows)
+ if subarea == "top-left":
+ x, y = x_0 + (width // cols) // 4, y_0 + (height // rows) // 4
+ elif subarea == "top":
+ x, y = x_0 + (width // cols) // 2, y_0 + (height // rows) // 4
+ elif subarea == "top-right":
+ x, y = x_0 + (width // cols) * 3 // 4, y_0 + (height // rows) // 4
+ elif subarea == "left":
+ x, y = x_0 + (width // cols) // 4, y_0 + (height // rows) // 2
+ elif subarea == "right":
+ x, y = x_0 + (width // cols) * 3 // 4, y_0 + (height // rows) // 2
+ elif subarea == "bottom-left":
+ x, y = x_0 + (width // cols) // 4, y_0 + (height // rows) * 3 // 4
+ elif subarea == "bottom":
+ x, y = x_0 + (width // cols) // 2, y_0 + (height // rows) * 3 // 4
+ elif subarea == "bottom-right":
+ x, y = x_0 + (width // cols) * 3 // 4, y_0 + (height // rows) * 3 // 4
+ else:
+ x, y = x_0 + (width // cols) // 2, y_0 + (height // rows) // 2
+ return x, y
+
+
+def elem_bbox_to_xy(bbox: tuple[tuple[int, int], tuple[int, int]]) -> tuple[int, int]:
+ tl, br = bbox
+ x, y = (tl[0] + br[0]) // 2, (tl[1] + br[1]) // 2
+ return x, y
+
+
+def reflect_parse_extarct(parsed_json: dict) -> ReflectOp:
+ decision = parsed_json.get("Decision")
+ if decision not in Decision.values():
+ op = ReflectOp(param_state=RunState.FAIL)
+ else:
+ op = ReflectOp(
+ decision=parsed_json.get("Decision"),
+ thought=parsed_json.get("Thought"),
+ documentation=parsed_json.get("Documentation"),
+ )
+ return op
+
+
+def screenshot_parse_extract(
+ parsed_json: dict, grid_on: bool = False
+) -> Union[BaseOpParam, BaseGridOpParam, GridOpParam]:
+ act = parsed_json.get("Action")
+ last_act = parsed_json.get("Summary")
+ act_name = act.split("(")[0]
+
+ if RunState.FINISH.value.upper() in act:
+ return BaseOpParam(param_state=RunState.FINISH)
+
+ if grid_on:
+ return screenshot_parse_extract_with_grid(act_name, act, last_act)
+ else:
+ return screenshot_parse_extract_without_grid(act_name, act, last_act)
+
+
+def op_params_clean(params: list[str]) -> list[Union[int, str]]:
+ param_values = []
+ for param_value in params:
+ if '"' in param_value or "'" in param_value: # remove `"`
+ param_values.append(param_value.strip()[1:-1])
+ else:
+ param_values.append(int(param_value))
+ return param_values
+
+
+def screenshot_parse_extract_without_grid(act_name: str, act: str, last_act: str) -> Union[BaseOpParam, GridOpParam]:
+ if act_name == ActionOp.TAP.value:
+ area = int(re.findall(r"tap\((.*?)\)", act)[0])
+ op = TapOpParam(act_name=act_name, area=area, last_act=last_act)
+ elif act_name == ActionOp.TEXT.value:
+ input_str = re.findall(r"text\((.*?)\)", act)[0][1:-1]
+ op = TextOpParam(act_name=act_name, input_str=input_str, last_act=last_act)
+ elif act_name == ActionOp.LONG_PRESS.value:
+ area = int(re.findall(r"long_press\((.*?)\)", act)[0])
+ op = LongPressOpParam(act_name=act_name, area=area, last_act=last_act)
+ elif act_name == ActionOp.SWIPE.value:
+ params = re.findall(r"swipe\((.*?)\)", act)[0].split(",")
+ params = op_params_clean(params) # area, swipe_orient, dist
+ op = SwipeOpParam(act_name=act_name, area=params[0], swipe_orient=params[1], dist=params[2], last_act=last_act)
+ elif act_name == ActionOp.GRID.value:
+ op = GridOpParam(act_name=act_name)
+ else:
+ op = BaseOpParam(param_state=RunState.FAIL)
+ return op
+
+
+def screenshot_parse_extract_with_grid(act_name: str, act: str, last_act: str) -> Union[BaseGridOpParam, GridOpParam]:
+ if act_name == ActionOp.TAP.value:
+ params = re.findall(r"tap\((.*?)\)", act)[0].split(",")
+ params = op_params_clean(params)
+ op = TapGridOpParam(act_name=act_name, area=params[0], subarea=params[1], last_act=last_act)
+ elif act_name == ActionOp.LONG_PRESS.value:
+ params = re.findall(r"long_press\((.*?)\)", act)[0].split(",")
+ params = op_params_clean(params)
+ op = LongPressGridOpParam(act_name=act_name, area=params[0], subarea=params[1], last_act=last_act)
+ elif act_name == ActionOp.SWIPE.value:
+ params = re.findall(r"swipe\((.*?)\)", act)[0].split(",")
+ params = op_params_clean(params)
+ op = SwipeGridOpParam(
+ act_name=act_name, start_area=params[0], start_subarea=params[1], end_area=params[2], end_subarea=params[3]
+ )
+ elif act_name == ActionOp.GRID.value:
+ op = GridOpParam(act_name=act_name)
+ else:
+ op = BaseGridOpParam(param_state=RunState.FAIL)
+ return op
diff --git a/metagpt/ext/sela/README.md b/metagpt/ext/sela/README.md
new file mode 100644
index 000000000..6fb47b42c
--- /dev/null
+++ b/metagpt/ext/sela/README.md
@@ -0,0 +1,106 @@
+# SELA: Tree-Search Enhanced LLM Agents for Automated Machine Learning
+
+
+Official implementation for paper [SELA: Tree-Search Enhanced LLM Agents for Automated Machine Learning](https://arxiv.org/abs/2410.17238).
+
+
+SELA is an innovative system that enhances Automated Machine Learning (AutoML) by integrating Monte Carlo Tree Search (MCTS) with LLM-based agents. Traditional AutoML methods often generate low-diversity and suboptimal code, limiting their effectiveness in model selection and ensembling. SELA addresses these challenges by representing pipeline configurations as trees, enabling agents to intelligently explore the solution space and iteratively refine their strategies based on experimental feedback.
+
+## 1. Data Preparation
+
+You can either download the datasets from the link or prepare the datasets from scratch.
+- **Download Datasets:** [Dataset Link](https://drive.google.com/drive/folders/151FIZoLygkRfeJgSI9fNMiLsixh1mK0r?usp=sharing)
+- **Download and prepare datasets from scratch:**
+ ```bash
+ cd data
+ python dataset.py --save_analysis_pool
+ python hf_data.py --save_analysis_pool
+ ```
+
+## 2. Configurations
+
+### Data Config
+
+- **`datasets.yaml`:** Provide base prompts, metrics, and target columns for respective datasets.
+- **`data.yaml`:** Modify `datasets_dir` to the base directory of all prepared datasets.
+
+### LLM Config
+
+```yaml
+llm:
+ api_type: 'openai'
+ model: deepseek-coder
+ base_url: "https://your_base_url"
+ api_key: sk-xxx
+ temperature: 0.5
+```
+
+
+## 3. SELA
+
+### Run SELA
+
+#### Setup
+
+```bash
+pip install -e .
+
+cd metagpt/ext/sela
+
+pip install -r requirements.txt
+```
+
+#### Running Experiments
+
+- **Examples:**
+ ```bash
+ python run_experiment.py --exp_mode mcts --task titanic --rollouts 10
+ python run_experiment.py --exp_mode mcts --task house-prices --rollouts 10 --low_is_better
+ ```
+
+#### Parameters
+
+- **`--rollouts`:** The number of rollouts.
+- **`--use_fixed_insights`:** Include fixed insights saved in `expo/insights/fixed_insights.json`.
+- **`--low_is_better`:** Use this if the dataset has a regression metric.
+- **`--from_scratch`:** Generate a new insight pool based on the dataset before running MCTS.
+- **`--role_timeout`:** Limits the duration of a single simulation (e.g., `10 rollouts with timeout 1,000` = max 10,000s).
+- **`--max_depth`:** Set the maximum depth of MCTS (default is 4).
+- **`--load_tree`:** Load an existing MCTS tree if the previous experiment was interrupted.
+ - Example:
+ ```bash
+ python run_experiment.py --exp_mode mcts --task titanic --rollouts 10
+ ```
+ - To resume:
+ ```bash
+ python run_experiment.py --exp_mode mcts --task titanic --rollouts 7 --load_tree
+ ```
+
+### Ablation Study
+
+**RandomSearch**
+
+- **Use a single insight:**
+ ```bash
+ python run_experiment.py --exp_mode rs --task titanic --rs_mode single
+ ```
+
+- **Use a set of insights:**
+ ```bash
+ python run_experiment.py --exp_mode rs --task titanic --rs_mode set
+ ```
+
+## 4. Citation
+Please cite our paper if you use SELA or find it cool or useful!
+
+```bibtex
+@misc{chi2024selatreesearchenhancedllm,
+ title={SELA: Tree-Search Enhanced LLM Agents for Automated Machine Learning},
+ author={Yizhou Chi and Yizhang Lin and Sirui Hong and Duyi Pan and Yaying Fei and Guanghao Mei and Bangbang Liu and Tianqi Pang and Jacky Kwok and Ceyao Zhang and Bang Liu and Chenglin Wu},
+ year={2024},
+ eprint={2410.17238},
+ archivePrefix={arXiv},
+ primaryClass={cs.AI},
+ url={https://arxiv.org/abs/2410.17238},
+}
+```
diff --git a/metagpt/ext/sela/data.yaml b/metagpt/ext/sela/data.yaml
new file mode 100644
index 000000000..7da5dbb3c
--- /dev/null
+++ b/metagpt/ext/sela/data.yaml
@@ -0,0 +1,3 @@
+datasets_dir: "path/to/datasets" # path to the datasets directory
+work_dir: ../../../workspace # path to the workspace directory
+role_dir: storage/SELA # path to the role directory
\ No newline at end of file
diff --git a/metagpt/ext/sela/data/custom_task.py b/metagpt/ext/sela/data/custom_task.py
new file mode 100644
index 000000000..08a7cbabb
--- /dev/null
+++ b/metagpt/ext/sela/data/custom_task.py
@@ -0,0 +1,74 @@
+import os
+
+from metagpt.ext.sela.data.dataset import SPECIAL_INSTRUCTIONS
+from metagpt.ext.sela.runner.mle_bench.instructions import (
+ ADDITIONAL_NOTES,
+ INSTRUCTIONS,
+ INSTRUCTIONS_OBFUSCATED,
+)
+
+MLE_BENCH_FILES = ["description.md", "description_obfuscated.md"]
+
+
+MLE_REQUIREMENTS = """
+{instructions}
+
+{additonal_notes}
+
+COMPETITION INSTRUCTIONS
+------
+
+{task_description}
+
+## More Instructions
+- You should split the training data into train and dev set with a seed of 42.
+- You should use the dev set to improve your model. Print the final dev set score after training.
+- output_dir: {output_dir}
+- Besides `submission.csv`, you should also save your `test_predictions.csv` and `dev_predictions.csv` in the output directory.
+- Note that `test_predictions.csv` should be identical to `submission.csv`.
+- Perform data analysis, data preprocessing, feature engineering, and modeling to predict the target. {special_instruction}
+**Do not make any plots or visualizations.**
+"""
+
+
+def get_mle_task_id(dataset_dir):
+ return dataset_dir.split("/")[-3]
+
+
+def get_mle_is_lower_better(task):
+ from mlebench.data import get_leaderboard
+ from mlebench.registry import registry
+
+ competition = registry.get_competition(task)
+ competition_leaderboard = get_leaderboard(competition)
+ return competition.grader.is_lower_better(competition_leaderboard)
+
+
+def get_mle_bench_requirements(dataset_dir, data_config, special_instruction, obfuscated=False):
+ work_dir = data_config["work_dir"]
+ task = get_mle_task_id(dataset_dir)
+ output_dir = f"{work_dir}/{task}"
+ final_output_dir = f"{work_dir}/submission"
+ os.makedirs(output_dir, exist_ok=True)
+ if special_instruction:
+ special_instruction = SPECIAL_INSTRUCTIONS[special_instruction]
+ else:
+ special_instruction = ""
+ if obfuscated:
+ instructions = INSTRUCTIONS_OBFUSCATED.format(dataset_dir=dataset_dir, output_dir=final_output_dir)
+ task_file = "description_obfuscated.md"
+ else:
+ instructions = INSTRUCTIONS.format(dataset_dir=dataset_dir, output_dir=output_dir)
+ task_file = "description.md"
+
+ with open(os.path.join(dataset_dir, task_file), encoding="utf-8") as f:
+ task_description = f.read()
+ mle_requirement = MLE_REQUIREMENTS.format(
+ instructions=instructions,
+ additonal_notes=ADDITIONAL_NOTES,
+ task_description=task_description,
+ output_dir=output_dir,
+ special_instruction=special_instruction,
+ )
+ print(mle_requirement)
+ return mle_requirement
diff --git a/metagpt/ext/sela/data/dataset.py b/metagpt/ext/sela/data/dataset.py
new file mode 100644
index 000000000..ef4179011
--- /dev/null
+++ b/metagpt/ext/sela/data/dataset.py
@@ -0,0 +1,395 @@
+import argparse
+import asyncio
+import json
+import os
+from pathlib import Path
+
+import openml
+import pandas as pd
+import yaml
+from sklearn.model_selection import train_test_split
+
+from metagpt.ext.sela.insights.solution_designer import SolutionDesigner
+from metagpt.ext.sela.utils import DATA_CONFIG
+
+BASE_USER_REQUIREMENT = """
+This is a {datasetname} dataset. Your goal is to predict the target column `{target_col}`.
+Perform data analysis, data preprocessing, feature engineering, and modeling to predict the target.
+Report {metric} on the eval data. Do not plot or make any visualizations.
+"""
+
+USE_AG = """
+- Please use autogluon for model training with presets='medium_quality', time_limit=None, give dev dataset to tuning_data, and use right eval_metric.
+"""
+
+TEXT_MODALITY = """
+- You could use models from transformers library for this text dataset.
+- Use gpu if available for faster training.
+"""
+
+IMAGE_MODALITY = """
+- You could use models from transformers/torchvision library for this image dataset.
+- Use gpu if available for faster training.
+"""
+
+STACKING = """
+- To avoid overfitting, train a weighted ensemble model such as StackingClassifier or StackingRegressor.
+- You could do some quick model prototyping to see which models work best and then use them in the ensemble.
+"""
+
+
+SPECIAL_INSTRUCTIONS = {"ag": USE_AG, "stacking": STACKING, "text": TEXT_MODALITY, "image": IMAGE_MODALITY}
+
+DI_INSTRUCTION = """
+## Attention
+1. Please do not leak the target label in any form during training.
+2. Test set does not have the target column.
+3. When conducting data exploration or analysis, print out the results of your findings.
+4. You should perform transformations on train, dev, and test sets at the same time (it's a good idea to define functions for this and avoid code repetition).
+5. When scaling or transforming features, make sure the target column is not included.
+6. You could utilize dev set to validate and improve model training. {special_instruction}
+
+## Saving Dev and Test Predictions
+1. Save the prediction results of BOTH the dev set and test set in `dev_predictions.csv` and `test_predictions.csv` respectively in the output directory.
+- Both files should contain a single column named `target` with the predicted values.
+2. Make sure the prediction results are in the same format as the target column in the original training set.
+- For instance, if the original target column is a list of string, the prediction results should also be strings.
+
+## Output Performance
+Print the train and dev set performance in the last step.
+
+# Output dir
+{output_dir}
+"""
+
+TASK_PROMPT = """
+# User requirement
+{user_requirement}
+{additional_instruction}
+# Data dir
+train set (with labels): {train_path}
+dev set (with labels): {dev_path}
+test set (without labels): {test_path}
+dataset description: {data_info_path} (During EDA, you can use this file to get additional information about the dataset)
+"""
+
+
+SEED = 100
+TRAIN_TEST_SPLIT = 0.8
+TRAIN_DEV_SPLIT = 0.75
+
+OPENML_DATASET_IDS = [
+ # reg
+ 41021,
+ 42727,
+ 41980,
+ 42225,
+ 531,
+ # cls
+ 41143,
+ 31,
+ 42733,
+ 41162,
+ 1067,
+ # multi cls
+ 40498,
+ 40982,
+ 12,
+ 40984,
+ 4538,
+]
+
+CUSTOM_DATASETS = [
+ ("04_titanic", "Survived"),
+ ("05_house-prices-advanced-regression-techniques", "SalePrice"),
+ ("06_santander-customer-transaction-prediction", "target"),
+ ("07_icr-identify-age-related-conditions", "Class"),
+]
+
+DSAGENT_DATASETS = [("concrete-strength", "Strength"), ("smoker-status", "smoking"), ("software-defects", "defects")]
+
+
+def get_split_dataset_path(dataset_name, config):
+ datasets_dir = config["datasets_dir"]
+ if dataset_name in config["datasets"]:
+ dataset = config["datasets"][dataset_name]
+ data_path = os.path.join(datasets_dir, dataset["dataset"])
+ split_datasets = {
+ "train": os.path.join(data_path, "split_train.csv"),
+ "dev": os.path.join(data_path, "split_dev.csv"),
+ "dev_wo_target": os.path.join(data_path, "split_dev_wo_target.csv"),
+ "dev_target": os.path.join(data_path, "split_dev_target.csv"),
+ "test": os.path.join(data_path, "split_test.csv"),
+ "test_wo_target": os.path.join(data_path, "split_test_wo_target.csv"),
+ "test_target": os.path.join(data_path, "split_test_target.csv"),
+ }
+ return split_datasets
+ else:
+ raise ValueError(
+ f"Dataset {dataset_name} not found in config file. Available datasets: {config['datasets'].keys()}"
+ )
+
+
+def get_user_requirement(task_name, config):
+ # datasets_dir = config["datasets_dir"]
+ if task_name in config["datasets"]:
+ dataset = config["datasets"][task_name]
+ # data_path = os.path.join(datasets_dir, dataset["dataset"])
+ user_requirement = dataset["user_requirement"]
+ return user_requirement
+ else:
+ raise ValueError(
+ f"Dataset {task_name} not found in config file. Available datasets: {config['datasets'].keys()}"
+ )
+
+
+def save_datasets_dict_to_yaml(datasets_dict, name="datasets.yaml"):
+ with open(name, "w") as file:
+ yaml.dump(datasets_dict, file)
+
+
+def create_dataset_dict(dataset):
+ dataset_dict = {
+ "dataset": dataset.name,
+ "user_requirement": dataset.create_base_requirement(),
+ "metric": dataset.get_metric(),
+ "target_col": dataset.target_col,
+ }
+ return dataset_dict
+
+
+def generate_di_instruction(output_dir, special_instruction):
+ if special_instruction:
+ special_instruction_prompt = SPECIAL_INSTRUCTIONS[special_instruction]
+ else:
+ special_instruction_prompt = ""
+ additional_instruction = DI_INSTRUCTION.format(
+ output_dir=output_dir, special_instruction=special_instruction_prompt
+ )
+ return additional_instruction
+
+
+def generate_task_requirement(task_name, data_config, is_di=True, special_instruction=None):
+ user_requirement = get_user_requirement(task_name, data_config)
+ split_dataset_path = get_split_dataset_path(task_name, data_config)
+ train_path = split_dataset_path["train"]
+ dev_path = split_dataset_path["dev"]
+ test_path = split_dataset_path["test_wo_target"]
+ work_dir = data_config["work_dir"]
+ output_dir = f"{work_dir}/{task_name}"
+ datasets_dir = data_config["datasets_dir"]
+ data_info_path = f"{datasets_dir}/{task_name}/dataset_info.json"
+ if is_di:
+ additional_instruction = generate_di_instruction(output_dir, special_instruction)
+ else:
+ additional_instruction = ""
+ user_requirement = TASK_PROMPT.format(
+ user_requirement=user_requirement,
+ train_path=train_path,
+ dev_path=dev_path,
+ test_path=test_path,
+ additional_instruction=additional_instruction,
+ data_info_path=data_info_path,
+ )
+ print(user_requirement)
+ return user_requirement
+
+
+class ExpDataset:
+ description: str = None
+ metadata: dict = None
+ dataset_dir: str = None
+ target_col: str = None
+ name: str = None
+
+ def __init__(self, name, dataset_dir, **kwargs):
+ self.name = name
+ self.dataset_dir = dataset_dir
+ self.target_col = kwargs.get("target_col", None)
+ self.force_update = kwargs.get("force_update", False)
+ self.save_dataset(target_col=self.target_col)
+
+ def check_dataset_exists(self):
+ fnames = [
+ "split_train.csv",
+ "split_dev.csv",
+ "split_test.csv",
+ "split_dev_wo_target.csv",
+ "split_dev_target.csv",
+ "split_test_wo_target.csv",
+ "split_test_target.csv",
+ ]
+ for fname in fnames:
+ if not os.path.exists(Path(self.dataset_dir, self.name, fname)):
+ return False
+ return True
+
+ def check_datasetinfo_exists(self):
+ return os.path.exists(Path(self.dataset_dir, self.name, "dataset_info.json"))
+
+ def get_raw_dataset(self):
+ raw_dir = Path(self.dataset_dir, self.name, "raw")
+ train_df = None
+ test_df = None
+ if not os.path.exists(Path(raw_dir, "train.csv")):
+ raise FileNotFoundError(f"Raw dataset `train.csv` not found in {raw_dir}")
+ else:
+ train_df = pd.read_csv(Path(raw_dir, "train.csv"))
+ if os.path.exists(Path(raw_dir, "test.csv")):
+ test_df = pd.read_csv(Path(raw_dir, "test.csv"))
+ return train_df, test_df
+
+ def get_dataset_info(self):
+ raw_df = pd.read_csv(Path(self.dataset_dir, self.name, "raw", "train.csv"))
+ metadata = {
+ "NumberOfClasses": raw_df[self.target_col].nunique(),
+ "NumberOfFeatures": raw_df.shape[1],
+ "NumberOfInstances": raw_df.shape[0],
+ "NumberOfInstancesWithMissingValues": int(raw_df.isnull().any(axis=1).sum()),
+ "NumberOfMissingValues": int(raw_df.isnull().sum().sum()),
+ "NumberOfNumericFeatures": raw_df.select_dtypes(include=["number"]).shape[1],
+ "NumberOfSymbolicFeatures": raw_df.select_dtypes(include=["object"]).shape[1],
+ }
+
+ df_head_text = self.get_df_head(raw_df)
+
+ dataset_info = {
+ "name": self.name,
+ "description": "",
+ "target_col": self.target_col,
+ "metadata": metadata,
+ "df_head": df_head_text,
+ }
+ return dataset_info
+
+ def get_df_head(self, raw_df):
+ return raw_df.head().to_string(index=False)
+
+ def get_metric(self):
+ dataset_info = self.get_dataset_info()
+ num_classes = dataset_info["metadata"]["NumberOfClasses"]
+ if num_classes == 2:
+ metric = "f1 binary"
+ elif 2 < num_classes <= 200:
+ metric = "f1 weighted"
+ elif num_classes > 200 or num_classes == 0:
+ metric = "rmse"
+ else:
+ raise ValueError(f"Number of classes {num_classes} not supported")
+ return metric
+
+ def create_base_requirement(self):
+ metric = self.get_metric()
+ req = BASE_USER_REQUIREMENT.format(datasetname=self.name, target_col=self.target_col, metric=metric)
+ return req
+
+ def save_dataset(self, target_col):
+ df, test_df = self.get_raw_dataset()
+ if not self.check_dataset_exists() or self.force_update:
+ print(f"Saving Dataset {self.name} in {self.dataset_dir}")
+ self.split_and_save(df, target_col, test_df=test_df)
+ else:
+ print(f"Dataset {self.name} already exists")
+ if not self.check_datasetinfo_exists() or self.force_update:
+ print(f"Saving Dataset info for {self.name}")
+ dataset_info = self.get_dataset_info()
+ self.save_datasetinfo(dataset_info)
+ else:
+ print(f"Dataset info for {self.name} already exists")
+
+ def save_datasetinfo(self, dataset_info):
+ with open(Path(self.dataset_dir, self.name, "dataset_info.json"), "w", encoding="utf-8") as file:
+ # utf-8 encoding is required
+ json.dump(dataset_info, file, indent=4, ensure_ascii=False)
+
+ def save_split_datasets(self, df, split, target_col=None):
+ path = Path(self.dataset_dir, self.name)
+ df.to_csv(Path(path, f"split_{split}.csv"), index=False)
+ if target_col:
+ df_wo_target = df.drop(columns=[target_col])
+ df_wo_target.to_csv(Path(path, f"split_{split}_wo_target.csv"), index=False)
+ df_target = df[[target_col]].copy()
+ if target_col != "target":
+ df_target["target"] = df_target[target_col]
+ df_target = df_target.drop(columns=[target_col])
+ df_target.to_csv(Path(path, f"split_{split}_target.csv"), index=False)
+
+ def split_and_save(self, df, target_col, test_df=None):
+ if not target_col:
+ raise ValueError("Target column not provided")
+ if test_df is None:
+ train, test = train_test_split(df, test_size=1 - TRAIN_TEST_SPLIT, random_state=SEED)
+ else:
+ train = df
+ test = test_df
+ train, dev = train_test_split(train, test_size=1 - TRAIN_DEV_SPLIT, random_state=SEED)
+ self.save_split_datasets(train, "train")
+ self.save_split_datasets(dev, "dev", target_col)
+ self.save_split_datasets(test, "test", target_col)
+
+
+class OpenMLExpDataset(ExpDataset):
+ def __init__(self, name, dataset_dir, dataset_id, **kwargs):
+ self.dataset_id = dataset_id
+ self.dataset = openml.datasets.get_dataset(
+ self.dataset_id, download_data=False, download_qualities=False, download_features_meta_data=True
+ )
+ self.name = self.dataset.name
+ self.target_col = self.dataset.default_target_attribute
+ super().__init__(self.name, dataset_dir, target_col=self.target_col, **kwargs)
+
+ def get_raw_dataset(self):
+ dataset = self.dataset
+ dataset_df, *_ = dataset.get_data()
+ raw_dir = Path(self.dataset_dir, self.name, "raw")
+ os.makedirs(raw_dir, exist_ok=True)
+ dataset_df.to_csv(Path(raw_dir, "train.csv"), index=False)
+ return dataset_df, None
+
+ def get_dataset_info(self):
+ dataset_info = super().get_dataset_info()
+ dataset = self.dataset
+ dataset_info["name"] = dataset.name
+ dataset_info["description"] = dataset.description
+ dataset_info["metadata"].update(dataset.qualities)
+ return dataset_info
+
+
+async def process_dataset(dataset, solution_designer: SolutionDesigner, save_analysis_pool, datasets_dict):
+ if save_analysis_pool:
+ await solution_designer.generate_solutions(dataset.get_dataset_info(), dataset.name)
+ dataset_dict = create_dataset_dict(dataset)
+ datasets_dict["datasets"][dataset.name] = dataset_dict
+
+
+def parse_args():
+ parser = argparse.ArgumentParser()
+ parser.add_argument("--force_update", action="store_true", help="Force update datasets")
+ parser.add_argument("--save_analysis_pool", action="store_true", help="Save analysis pool")
+ parser.add_argument(
+ "--no_save_analysis_pool", dest="save_analysis_pool", action="store_false", help="Do not save analysis pool"
+ )
+ parser.set_defaults(save_analysis_pool=True)
+ return parser.parse_args()
+
+
+if __name__ == "__main__":
+ datasets_dir = DATA_CONFIG["datasets_dir"]
+ args = parse_args()
+ force_update = args.force_update
+ save_analysis_pool = args.save_analysis_pool
+ datasets_dict = {"datasets": {}}
+ solution_designer = SolutionDesigner()
+ for dataset_id in OPENML_DATASET_IDS:
+ openml_dataset = OpenMLExpDataset("", datasets_dir, dataset_id, force_update=force_update)
+ asyncio.run(process_dataset(openml_dataset, solution_designer, save_analysis_pool, datasets_dict))
+
+ for dataset_name, target_col in CUSTOM_DATASETS:
+ custom_dataset = ExpDataset(dataset_name, datasets_dir, target_col=target_col, force_update=force_update)
+ asyncio.run(process_dataset(custom_dataset, solution_designer, save_analysis_pool, datasets_dict))
+
+ for dataset_name, target_col in DSAGENT_DATASETS:
+ custom_dataset = ExpDataset(dataset_name, datasets_dir, target_col=target_col, force_update=force_update)
+ asyncio.run(process_dataset(custom_dataset, solution_designer, save_analysis_pool, datasets_dict))
+
+ save_datasets_dict_to_yaml(datasets_dict)
diff --git a/metagpt/ext/sela/data/hf_data.py b/metagpt/ext/sela/data/hf_data.py
new file mode 100644
index 000000000..9645796af
--- /dev/null
+++ b/metagpt/ext/sela/data/hf_data.py
@@ -0,0 +1,140 @@
+import asyncio
+import io
+import os
+from pathlib import Path
+
+import pandas as pd
+from datasets import load_dataset
+from PIL import Image
+
+from metagpt.ext.sela.data.dataset import (
+ ExpDataset,
+ parse_args,
+ process_dataset,
+ save_datasets_dict_to_yaml,
+)
+from metagpt.ext.sela.insights.solution_designer import SolutionDesigner
+from metagpt.ext.sela.utils import DATA_CONFIG
+
+HFDATSETS = [
+ {"name": "sms_spam", "dataset_name": "ucirvine/sms_spam", "target_col": "label", "modality": "text"},
+ {"name": "banking77", "dataset_name": "PolyAI/banking77", "target_col": "label", "modality": "text"},
+ {"name": "gnad10", "dataset_name": "community-datasets/gnad10", "target_col": "label", "modality": "text"},
+ {
+ "name": "oxford-iiit-pet",
+ "dataset_name": "timm/oxford-iiit-pet",
+ "image_col": "image",
+ "target_col": "label",
+ "modality": "image",
+ },
+ {
+ "name": "stanford_cars",
+ "dataset_name": "tanganke/stanford_cars",
+ "image_col": "image",
+ "target_col": "label",
+ "modality": "image",
+ },
+ {
+ "name": "fashion_mnist",
+ "dataset_name": "zalando-datasets/fashion_mnist",
+ "image_col": "image",
+ "target_col": "label",
+ "modality": "image",
+ },
+]
+
+
+class HFExpDataset(ExpDataset):
+ train_ratio = 0.6
+ dev_ratio = 0.2
+ test_ratio = 0.2
+
+ def __init__(self, name, dataset_dir, dataset_name, **kwargs):
+ self.name = name
+ self.dataset_dir = dataset_dir
+ self.dataset_name = dataset_name
+ self.modality = kwargs.get("modality", "")
+ self.target_col = kwargs.get("target_col", "label")
+ self.image_col = kwargs.get("image_col", "image")
+ self.dataset = load_dataset(self.dataset_name, trust_remote_code=True)
+ super().__init__(self.name, dataset_dir, **kwargs)
+
+ def get_raw_dataset(self):
+ raw_dir = Path(self.dataset_dir, self.name, "raw")
+ raw_dir.mkdir(parents=True, exist_ok=True)
+
+ if os.path.exists(Path(raw_dir, "train.csv")):
+ df = pd.read_csv(Path(raw_dir, "train.csv"), encoding="utf-8")
+ else:
+ df = self.dataset["train"].to_pandas()
+
+ if self.modality == "image":
+ df = self.save_images_and_update_df(df, raw_dir, "train")
+
+ df.to_csv(Path(raw_dir, "train.csv"), index=False, encoding="utf-8")
+
+ if os.path.exists(Path(raw_dir, "test.csv")):
+ test_df = pd.read_csv(Path(raw_dir, "test.csv"), encoding="utf-8")
+ else:
+ if self.dataset and "test" in self.dataset:
+ test_df = self.dataset["test"].to_pandas()
+
+ if self.modality == "image":
+ test_df = self.save_images_and_update_df(test_df, raw_dir, "test")
+
+ test_df.to_csv(Path(raw_dir, "test.csv"), index=False, encoding="utf-8")
+ else:
+ test_df = None
+
+ return df, test_df
+
+ def save_images_and_update_df(self, df, raw_dir, split):
+ abs_image_dir = Path(raw_dir, f"{split}_images")
+ rel_image_dir = f"raw/{split}_images"
+ abs_image_dir.mkdir(parents=True, exist_ok=True)
+
+ def process_image(idx, row):
+ image_bytes = row[self.image_col]["bytes"]
+ image = Image.open(io.BytesIO(image_bytes))
+ if image.mode == "RGBA":
+ image = image.convert("RGB")
+ img_path = Path(abs_image_dir, f"{idx}.jpg")
+ rel_img_path = f"{rel_image_dir}/{idx}.jpg"
+ image.save(img_path)
+ return rel_img_path
+
+ df["image"] = df.apply(lambda row: process_image(row.name, row), axis=1)
+ return df
+
+ def get_df_head(self, raw_df):
+ examples = []
+ for i in range(5):
+ examples.append(raw_df.iloc[i].to_dict())
+ return examples
+
+ def get_dataset_info(self):
+ dataset_info = super().get_dataset_info()
+ dataset = self.dataset
+ dataset_info["description"] = dataset["train"].info.description
+ return dataset_info
+
+
+if __name__ == "__main__":
+ dataset_dir = DATA_CONFIG["datasets_dir"]
+ args = parse_args()
+ force_update = args.force_update
+ save_analysis_pool = args.save_analysis_pool
+ datasets_dict = {"datasets": {}}
+ solution_designer = SolutionDesigner()
+ for dataset_meta in HFDATSETS:
+ hf_dataset = HFExpDataset(
+ dataset_meta["name"],
+ dataset_dir,
+ dataset_meta["dataset_name"],
+ target_col=dataset_meta["target_col"],
+ image_col=dataset_meta.get("image_col", ""),
+ force_update=force_update,
+ modality=dataset_meta["modality"],
+ )
+ asyncio.run(process_dataset(hf_dataset, solution_designer, save_analysis_pool, datasets_dict))
+ save_datasets_dict_to_yaml(datasets_dict, "hf_datasets.yaml")
diff --git a/metagpt/ext/sela/datasets.yaml b/metagpt/ext/sela/datasets.yaml
new file mode 100644
index 000000000..2d02951d4
--- /dev/null
+++ b/metagpt/ext/sela/datasets.yaml
@@ -0,0 +1,225 @@
+datasets:
+ titanic:
+ dataset: 04_titanic
+ metric: f1
+ target_col: Survived
+ user_requirement: "This is a 04_titanic dataset. Your goal is to predict the target\
+ \ column `Survived`.\nPerform data analysis, data preprocessing, feature engineering,\
+ \ and modeling to predict the target. \nReport f1 on the eval data. Do not plot\
+ \ or make any visualizations.\n"
+ house-prices:
+ dataset: 05_house-prices-advanced-regression-techniques
+ metric: rmse
+ target_col: SalePrice
+ user_requirement: "This is a 05_house-prices-advanced-regression-techniques dataset.\
+ \ Your goal is to predict the target column `SalePrice`.\nPerform data analysis,\
+ \ data preprocessing, feature engineering, and modeling to predict the target.\
+ \ \nReport rmse on the eval data. Do not plot or make any visualizations.\n"
+ santander-customer:
+ dataset: 06_santander-customer-transaction-prediction
+ metric: f1
+ target_col: target
+ user_requirement: "This is a 06_santander-customer-transaction-prediction dataset.\
+ \ Your goal is to predict the target column `target`.\nPerform data analysis,\
+ \ data preprocessing, feature engineering, and modeling to predict the target.\
+ \ \nReport f1 on the eval data. Do not plot or make any visualizations.\n"
+ icr:
+ dataset: 07_icr-identify-age-related-conditions
+ metric: f1
+ target_col: Class
+ user_requirement: "This is a 07_icr-identify-age-related-conditions dataset. Your\
+ \ goal is to predict the target column `Class`.\nPerform data analysis, data\
+ \ preprocessing, feature engineering, and modeling to predict the target. \n\
+ Report f1 on the eval data. Do not plot or make any visualizations.\n"
+ Click_prediction_small:
+ dataset: Click_prediction_small
+ metric: f1
+ target_col: click
+ user_requirement: "This is a Click_prediction_small dataset. Your goal is to predict\
+ \ the target column `click`.\nPerform data analysis, data preprocessing, feature\
+ \ engineering, and modeling to predict the target. \nReport f1 on the eval data.\
+ \ Do not plot or make any visualizations.\n"
+ GesturePhaseSegmentationProcessed:
+ dataset: GesturePhaseSegmentationProcessed
+ metric: f1 weighted
+ target_col: Phase
+ user_requirement: "This is a GesturePhaseSegmentationProcessed dataset. Your goal\
+ \ is to predict the target column `Phase`.\nPerform data analysis, data preprocessing,\
+ \ feature engineering, and modeling to predict the target. \nReport f1 weighted\
+ \ on the eval data. Do not plot or make any visualizations.\n"
+ Moneyball:
+ dataset: Moneyball
+ metric: rmse
+ target_col: RS
+ user_requirement: "This is a Moneyball dataset. Your goal is to predict the target\
+ \ column `RS`.\nPerform data analysis, data preprocessing, feature engineering,\
+ \ and modeling to predict the target. \nReport rmse on the eval data. Do not\
+ \ plot or make any visualizations.\n"
+ SAT11-HAND-runtime-regression:
+ dataset: SAT11-HAND-runtime-regression
+ metric: rmse
+ target_col: runtime
+ user_requirement: "This is a SAT11-HAND-runtime-regression dataset. Your goal\
+ \ is to predict the target column `runtime`.\nPerform data analysis, data preprocessing,\
+ \ feature engineering, and modeling to predict the target. \nReport rmse on\
+ \ the eval data. Do not plot or make any visualizations.\n"
+ boston:
+ dataset: boston
+ metric: rmse
+ target_col: MEDV
+ user_requirement: "This is a boston dataset. Your goal is to predict the target\
+ \ column `MEDV`.\nPerform data analysis, data preprocessing, feature engineering,\
+ \ and modeling to predict the target. \nReport rmse on the eval data. Do not\
+ \ plot or make any visualizations.\n"
+ colleges:
+ dataset: colleges
+ metric: rmse
+ target_col: percent_pell_grant
+ user_requirement: "This is a colleges dataset. Your goal is to predict the target\
+ \ column `percent_pell_grant`.\nPerform data analysis, data preprocessing, feature\
+ \ engineering, and modeling to predict the target. \nReport rmse on the eval\
+ \ data. Do not plot or make any visualizations.\n"
+ concrete-strength:
+ dataset: concrete-strength
+ metric: rmse
+ target_col: Strength
+ user_requirement: "This is a concrete-strength dataset. Your goal is to predict\
+ \ the target column `Strength`.\nPerform data analysis, data preprocessing,\
+ \ feature engineering, and modeling to predict the target. \nReport rmse on\
+ \ the eval data. Do not plot or make any visualizations.\n"
+ credit-g:
+ dataset: credit-g
+ metric: f1
+ target_col: class
+ user_requirement: "This is a credit-g dataset. Your goal is to predict the target\
+ \ column `class`.\nPerform data analysis, data preprocessing, feature engineering,\
+ \ and modeling to predict the target. \nReport f1 on the eval data. Do not plot\
+ \ or make any visualizations.\n"
+ diamonds:
+ dataset: diamonds
+ metric: rmse
+ target_col: price
+ user_requirement: "This is a diamonds dataset. Your goal is to predict the target\
+ \ column `price`.\nPerform data analysis, data preprocessing, feature engineering,\
+ \ and modeling to predict the target. \nReport rmse on the eval data. Do not\
+ \ plot or make any visualizations.\n"
+ jasmine:
+ dataset: jasmine
+ metric: f1
+ target_col: class
+ user_requirement: "This is a jasmine dataset. Your goal is to predict the target\
+ \ column `class`.\nPerform data analysis, data preprocessing, feature engineering,\
+ \ and modeling to predict the target. \nReport f1 on the eval data. Do not plot\
+ \ or make any visualizations.\n"
+ kc1:
+ dataset: kc1
+ metric: f1
+ target_col: defects
+ user_requirement: "This is a kc1 dataset. Your goal is to predict the target column\
+ \ `defects`.\nPerform data analysis, data preprocessing, feature engineering,\
+ \ and modeling to predict the target. \nReport f1 on the eval data. Do not plot\
+ \ or make any visualizations.\n"
+ kick:
+ dataset: kick
+ metric: f1
+ target_col: IsBadBuy
+ user_requirement: "This is a kick dataset. Your goal is to predict the target\
+ \ column `IsBadBuy`.\nPerform data analysis, data preprocessing, feature engineering,\
+ \ and modeling to predict the target. \nReport f1 on the eval data. Do not plot\
+ \ or make any visualizations.\n"
+ mfeat-factors:
+ dataset: mfeat-factors
+ metric: f1 weighted
+ target_col: class
+ user_requirement: "This is a mfeat-factors dataset. Your goal is to predict the\
+ \ target column `class`.\nPerform data analysis, data preprocessing, feature\
+ \ engineering, and modeling to predict the target. \nReport f1 weighted on the\
+ \ eval data. Do not plot or make any visualizations.\n"
+ segment:
+ dataset: segment
+ metric: f1 weighted
+ target_col: class
+ user_requirement: "This is a segment dataset. Your goal is to predict the target\
+ \ column `class`.\nPerform data analysis, data preprocessing, feature engineering,\
+ \ and modeling to predict the target. \nReport f1 weighted on the eval data.\
+ \ Do not plot or make any visualizations.\n"
+ smoker-status:
+ dataset: smoker-status
+ metric: f1
+ target_col: smoking
+ user_requirement: "This is a smoker-status dataset. Your goal is to predict the\
+ \ target column `smoking`.\nPerform data analysis, data preprocessing, feature\
+ \ engineering, and modeling to predict the target. \nReport f1 on the eval data.\
+ \ Do not plot or make any visualizations.\n"
+ software-defects:
+ dataset: software-defects
+ metric: f1
+ target_col: defects
+ user_requirement: "This is a software-defects dataset. Your goal is to predict\
+ \ the target column `defects`.\nPerform data analysis, data preprocessing, feature\
+ \ engineering, and modeling to predict the target. \nReport f1 on the eval data.\
+ \ Do not plot or make any visualizations.\n"
+ steel-plates-fault:
+ dataset: steel-plates-fault
+ metric: f1 weighted
+ target_col: target
+ user_requirement: "This is a steel-plates-fault dataset. Your goal is to predict\
+ \ the target column `target`.\nPerform data analysis, data preprocessing, feature\
+ \ engineering, and modeling to predict the target. \nReport f1 weighted on the\
+ \ eval data. Do not plot or make any visualizations.\n"
+ wine-quality-white:
+ dataset: wine-quality-white
+ metric: f1 weighted
+ target_col: Class
+ user_requirement: "This is a wine-quality-white dataset. Your goal is to predict\
+ \ the target column `Class`.\nPerform data analysis, data preprocessing, feature\
+ \ engineering, and modeling to predict the target. \nReport f1 weighted on the\
+ \ eval data. Do not plot or make any visualizations.\n"
+ banking77:
+ dataset: banking77
+ metric: f1 weighted
+ target_col: label
+ user_requirement: "This is a banking77 dataset. Your goal is to predict the target\
+ \ column `label`.\nPerform data analysis, data preprocessing, feature engineering,\
+ \ and modeling to predict the target. \nReport f1 weighted on the eval data.\
+ \ Do not plot or make any visualizations.\n"
+ fashion_mnist:
+ dataset: fashion_mnist
+ metric: f1 weighted
+ target_col: label
+ user_requirement: "This is a fashion_mnist dataset. Your goal is to predict the\
+ \ target column `label`.\nPerform data analysis, data preprocessing, feature\
+ \ engineering, and modeling to predict the target. \nReport f1 weighted on the\
+ \ eval data. Do not plot or make any visualizations.\n"
+ gnad10:
+ dataset: gnad10
+ metric: f1 weighted
+ target_col: label
+ user_requirement: "This is a gnad10 dataset. Your goal is to predict the target\
+ \ column `label`.\nPerform data analysis, data preprocessing, feature engineering,\
+ \ and modeling to predict the target. \nReport f1 weighted on the eval data.\
+ \ Do not plot or make any visualizations.\n"
+ oxford-iiit-pet:
+ dataset: oxford-iiit-pet
+ metric: f1 weighted
+ target_col: label
+ user_requirement: "This is a oxford-iiit-pet dataset. Your goal is to predict\
+ \ the target column `label`.\nPerform data analysis, data preprocessing,\
+ \ feature engineering, and modeling to predict the target. \nReport f1 weighted on the\
+ \ eval data. Do not plot or make any visualizations.\n"
+ sms_spam:
+ dataset: sms_spam
+ metric: f1
+ target_col: label
+ user_requirement: "This is a sms_spam dataset. Your goal is to predict the target\
+ \ column `label`.\nPerform data analysis, data preprocessing, feature engineering,\
+ \ and modeling to predict the target. \nReport f1 on the eval data. Do not plot\
+ \ or make any visualizations.\n"
+ stanford_cars:
+ dataset: stanford_cars
+ metric: f1 weighted
+ target_col: label
+ user_requirement: "This is a stanford_cars dataset. Your goal is to predict the\
+ \ target column `label`.\nPerform data analysis, data preprocessing, feature\
+ \ engineering, and modeling to predict the target. \nReport f1 weighted on the\
+ \ eval data. Do not plot or make any visualizations.\n"
diff --git a/metagpt/ext/sela/evaluation/evaluation.py b/metagpt/ext/sela/evaluation/evaluation.py
new file mode 100644
index 000000000..1e58e1725
--- /dev/null
+++ b/metagpt/ext/sela/evaluation/evaluation.py
@@ -0,0 +1,49 @@
+from pathlib import Path
+
+import numpy as np
+from sklearn.metrics import accuracy_score, f1_score, mean_squared_error, roc_auc_score
+
+
+def evaluate_score(pred, gt, metric):
+ if metric == "accuracy":
+ return accuracy_score(gt, pred)
+ elif metric == "f1":
+ unique_classes = sorted(list(np.unique(gt)))
+ if 1 in unique_classes and 0 in unique_classes:
+ pos_label = 1
+ else:
+ pos_label = unique_classes[0] if len(unique_classes) == 2 else None
+ return f1_score(gt, pred, pos_label=pos_label)
+ elif metric == "f1 weighted":
+ return f1_score(gt, pred, average="weighted")
+ elif metric == "roc_auc":
+ return roc_auc_score(gt, pred)
+ elif metric == "rmse":
+ return mean_squared_error(gt, pred, squared=False)
+ elif metric == "log rmse":
+ return mean_squared_error(np.log1p(gt), np.log1p(pred), squared=False)
+ else:
+ raise ValueError(f"Metric {metric} not supported")
+
+
+def node_evaluate_score_sela(node):
+ preds = node.get_and_move_predictions("test")["target"]
+ gt = node.get_gt("test")["target"]
+ metric = node.state["dataset_config"]["metric"]
+ return evaluate_score(preds, gt, metric)
+
+
+def node_evaluate_score_mlebench(node):
+ # TODO
+ from mlebench.grade import grade_csv
+ from mlebench.registry import registry
+
+ competition_id = node.state["task"]
+ data_dir = Path(node.state["custom_dataset_dir"]).parent.parent.parent # prepared/public/../../../
+ pred_path = node.get_predictions_path("test")
+ new_registry = registry.set_data_dir(data_dir)
+ competition = new_registry.get_competition(competition_id)
+ submission = Path(pred_path)
+ report = grade_csv(submission, competition).to_dict()
+ report["submission_path"] = str(submission)
+ return report
diff --git a/metagpt/ext/sela/evaluation/visualize_mcts.py b/metagpt/ext/sela/evaluation/visualize_mcts.py
new file mode 100644
index 000000000..6f803a91c
--- /dev/null
+++ b/metagpt/ext/sela/evaluation/visualize_mcts.py
@@ -0,0 +1,163 @@
+import textwrap
+
+import matplotlib.pyplot as plt
+import networkx as nx
+
+from metagpt.ext.sela.search.tree_search import Node
+
+NODE_TEMPLATE = """\
+[Node {id}]
+Plans:
+{plans}
+Simulated: {simulated}
+Score: {score}, Visits: {num_visits}
+
+"""
+
+NODE_SIZE = 12000
+NODE_FONT_SIZE = 18
+
+
+def get_role_plans(role):
+ plans = role.planner.plan.tasks
+ instruct_plans = [f"{i+1}. {task.instruction}" for i, task in enumerate(plans)]
+ return instruct_plans
+
+
+def get_tree_text(node: Node):
+ role_dict = {}
+ code_set = set()
+
+ def load_role(node):
+ if node.id not in role_dict:
+ role_dict[node.id] = node.load_role()
+ return role_dict[node.id]
+
+ def visualize_node(node: Node, previous_plans=None):
+ role = load_role(node)
+ node_id = node.id
+ plans = role.planner.plan.tasks
+ instruct_plans = [f"{i+1}. {task.instruction}" for i, task in enumerate(plans)]
+ if previous_plans is not None:
+ instruct_plans = [plan for plan, prev_plan in zip(instruct_plans, previous_plans) if plan != prev_plan]
+ instruct_plans_text = "\n".join(instruct_plans)
+ simulated = role.state_saved
+ score = f"avg score: {node.avg_value()}, simulated score: {node.raw_reward}"
+ num_visits = node.visited
+ return NODE_TEMPLATE.format(
+ id=node_id, plans=instruct_plans_text, simulated=simulated, score=score, num_visits=num_visits
+ )
+
+ def visualize_tree_text(node, depth=0, previous_plans=None):
+ text = ""
+ if node is not None:
+ text += visualize_node(node, previous_plans)
+ role = load_role(node)
+ code_set.update({task.instruction for task in role.planner.plan.tasks})
+ previous_plans = get_role_plans(role)
+ for child in node.children:
+ text += textwrap.indent(visualize_tree_text(child, depth + 1, previous_plans), "\t")
+ return text
+
+ num_simulations = node.visited
+ text = f"Number of simulations: {num_simulations}\n"
+ text += visualize_tree_text(node)
+ return text, len(code_set)
+
+
+def get_node_color(node):
+ if node["visits"] == 0:
+ return "#D3D3D3"
+ else:
+ # The higher the avg_value, the more intense the color
+ # avg_value is between 0 and 1
+ avg_value = node["avg_value"]
+ # Convert avg_value to a color ranging from red (low) to green (high)
+ red = int(255 * (1 - avg_value))
+ green = int(255 * avg_value)
+ return f"#{red:02X}{green:02X}00"
+
+
+def visualize_tree(graph, show_instructions=False, save_path=""):
+ # Use a hierarchical layout for tree-like visualization
+ pos = nx.spring_layout(graph, k=0.9, iterations=50)
+
+ plt.figure(figsize=(30, 20)) # Further increase figure size for better visibility
+
+ # Calculate node levels
+ root = "0"
+ levels = nx.single_source_shortest_path_length(graph, root)
+ max_level = max(levels.values())
+
+ # Adjust y-coordinates based on levels and x-coordinates to prevent overlap
+ nodes_by_level = {}
+ for node, level in levels.items():
+ if level not in nodes_by_level:
+ nodes_by_level[level] = []
+ nodes_by_level[level].append(node)
+
+ for level, nodes in nodes_by_level.items():
+ y = 1 - level / max_level
+ x_step = 1.0 / (len(nodes) + 1)
+ for i, node in enumerate(sorted(nodes)):
+ pos[node] = ((i + 1) * x_step, y)
+
+ # Draw edges
+ nx.draw_networkx_edges(graph, pos, edge_color="gray", arrows=True, arrowsize=40, width=3)
+
+ # Draw nodes
+ node_colors = [get_node_color(graph.nodes[node]) for node in graph.nodes]
+ nx.draw_networkx_nodes(graph, pos, node_size=NODE_SIZE, node_color=node_colors)
+
+ # Add labels to nodes
+ labels = nx.get_node_attributes(graph, "label")
+ nx.draw_networkx_labels(graph, pos, labels, font_size=NODE_FONT_SIZE)
+
+ if show_instructions:
+ # Add instructions to the right side of nodes
+ instructions = nx.get_node_attributes(graph, "instruction")
+ for node, (x, y) in pos.items():
+ wrapped_text = textwrap.fill(instructions[node], width=30) # Adjust width as needed
+ plt.text(x + 0.05, y, wrapped_text, fontsize=15, ha="left", va="center")
+
+ plt.title("MCTS Tree Visualization", fontsize=40)
+ plt.axis("off") # Turn off axis
+ plt.tight_layout()
+ if save_path:
+ plt.savefig(save_path)
+ plt.show()
+
+
+def build_tree_recursive(graph, parent_id, node, node_order, start_task_id=2):
+ """
+ Recursively builds the entire tree starting from the root node.
+ Adds nodes and edges to the NetworkX graph.
+ """
+ role = node.load_role()
+ depth = node.get_depth()
+ if depth == 0:
+ instruction = "\n\n".join([role.planner.plan.tasks[i].instruction for i in range(start_task_id)])
+ else:
+ instruction = role.planner.plan.tasks[depth + start_task_id - 1].instruction
+ print(instruction)
+ # Add the current node with attributes to the graph
+ dev_score = node.raw_reward.get("dev_score", 0) * 100
+ avg_score = node.avg_value() * 100
+ order = node_order.index(node.id) if node.id in node_order else ""
+ graph.add_node(
+ parent_id,
+ label=f"{node.id}\nAvg: {avg_score:.1f}\nScore: {dev_score:.1f}\nVisits: {node.visited}\nOrder: {order}",
+ avg_value=node.avg_value(),
+ dev_score=dev_score,
+ visits=node.visited,
+ instruction=instruction,
+ )
+ # Stopping condition: if the node has no children, return
+ if not node.children:
+ return
+
+ # Recursively create all child nodes
+ for i, child in enumerate(node.children):
+ child_id = f"{parent_id}-{i}"
+ graph.add_edge(parent_id, child_id)
+ build_tree_recursive(graph, child_id, child, node_order)
diff --git a/metagpt/ext/sela/experimenter.py b/metagpt/ext/sela/experimenter.py
new file mode 100644
index 000000000..b05ea2fc3
--- /dev/null
+++ b/metagpt/ext/sela/experimenter.py
@@ -0,0 +1,195 @@
+from __future__ import annotations
+
+import asyncio
+import json
+import os
+
+from pydantic import model_validator
+
+from metagpt.actions.di.write_analysis_code import WriteAnalysisCode
+from metagpt.const import SERDESER_PATH
+from metagpt.ext.sela.utils import mcts_logger, save_notebook
+from metagpt.roles.di.data_interpreter import DataInterpreter
+from metagpt.schema import Message, Task, TaskResult
+from metagpt.utils.common import CodeParser, write_json_file
+
+CODE_BLOCK_RESULT = """
+## Code:
+{code}
+
+## Execution Result:
+{result}
+"""
+
+EXTRACT_SCORE_PROMPT = """
+# Code Blocks
+{code_block}
+# Instruction:
+Based on the code and execution result, please extract the **final scores** and return it as a dictionary.
+If you cannot find the scores, please still return a dictionary with the keys 'train_score', 'dev_score', and 'test_score', and set the values to -1.
+
+# Format:
+```json
+{{
+ "train_score": x.x,
+ "dev_score": x.x,
+ "test_score": x.x
+}}
+```
+"""
+
+
+class TimeoutException(Exception):
+ pass
+
+
+def async_timeout():
+ def decorator(func):
+ async def wrapper(self, *args, **kwargs):
+ try:
+ result = await asyncio.wait_for(func(self, *args, **kwargs), timeout=self.role_timeout)
+ except asyncio.TimeoutError:
+ text = f"Function timed out after {self.role_timeout} seconds"
+ mcts_logger.error(text)
+ self.save_state()
+ raise TimeoutException(text)
+ return result
+
+ return wrapper
+
+ return decorator
+
+
+class Experimenter(DataInterpreter):
+ node_id: str = "0"
+ start_task_id: int = 1
+ state_saved: bool = False
+ role_dir: str = SERDESER_PATH.joinpath("team", "environment", "roles", "Experimenter")
+ role_timeout: int = 1000
+
+ def get_node_name(self):
+ return f"Node-{self.node_id}"
+
+ def get_next_instruction(self):
+ return self.planner.plan.tasks[self.start_task_id].instruction
+
+ def change_next_instruction(self, new_instruction):
+ if new_instruction is not None:
+ self.planner.plan.task_map[str(self.start_task_id)].instruction = new_instruction
+ self.remap_tasks()
+
+ def update_til_start_task(self, role: Experimenter, backward: bool = True):
+ if backward:
+ # make sure the previous task instructions are matched
+ assert (
+ self.start_task_id == role.start_task_id - 1
+ ), f"start_task_id: {self.start_task_id}, role.start_task_id: {role.start_task_id}"
+ for i in range(self.start_task_id):
+ if (
+ self.planner.plan.task_map[str(self.start_task_id)].instruction
+ != role.planner.plan.task_map[str(self.start_task_id)].instruction
+ ):
+ mcts_logger.info("Previous task instructions not matched")
+ self.remap_tasks()
+ return
+ # copy new role's task (self.start_task_id) to current role
+ self.planner.plan.task_map[str(self.start_task_id)] = role.planner.plan.task_map[
+ str(self.start_task_id)
+ ].model_copy()
+ self.remap_tasks()
+
+ else:
+ assert (
+ self.start_task_id == role.start_task_id + 1
+ ), f"start_task_id: {self.start_task_id}, role.start_task_id: {role.start_task_id}"
+ if int(role.planner.plan.current_task_id) > self.start_task_id:
+ for i in range(role.start_task_id):
+ self.planner.plan.task_map[str(i)] = role.planner.plan.task_map[str(i)].model_copy()
+ self.remap_tasks()
+
+ async def get_score(self):
+ score_dict = await self.llm_extract_score()
+ score_dict["score"] = score_dict["dev_score"]
+ return score_dict
+
+ async def llm_extract_score(self):
+ # result_text = self.planner.plan.task_map[str(len(self.planner.plan.task_map))].result
+ # code_text = self.planner.plan.task_map[str(len(self.planner.plan.task_map))].code
+ num_tasks = len(self.planner.plan.task_map)
+ task_map = self.planner.plan.task_map
+ code_block = "\n".join(
+ [
+ CODE_BLOCK_RESULT.format(code=task_map[str(i + 1)].code, result=task_map[str(i + 1)].result)
+ for i in range(num_tasks)
+ ]
+ )
+ rsp = await self.llm.aask(EXTRACT_SCORE_PROMPT.format(code_block=code_block, role="user"))
+ json_block = CodeParser.parse_code(block=None, text=rsp)
+ score_dict = json.loads(json_block)
+ return score_dict
+
+ @model_validator(mode="after")
+ def set_plan_and_tool(self) -> "Interpreter":
+ if self.planner.plan.goal != "":
+ self.set_actions([WriteAnalysisCode])
+ self._set_state(0)
+ print("Plan already exists, skipping initialization.")
+ return self
+ print("Initializing plan and tool...")
+ return super().set_plan_and_tool()
+
+ async def _act_on_task(self, current_task: Task) -> TaskResult:
+ """Useful in 'plan_and_act' mode. Wrap the output in a TaskResult for review and confirmation."""
+ mcts_logger.info(f"The current_task is: {current_task}")
+ code, result, is_success = await self._write_and_exec_code()
+ task_result = TaskResult(code=code, result=result, is_success=is_success)
+ if int(current_task.task_id) == self.start_task_id + 1:
+ # fe_id = current_task.dependent_task_ids
+ self.save_state()
+ save_notebook(role=self, save_dir=self.role_dir, name=self.get_node_name(), save_to_depth=True)
+ else:
+ save_notebook(role=self, save_dir=self.role_dir, name=self.get_node_name())
+ return task_result
+
+ def get_solution(self):
+ codes = [task.code for task in self.planner.plan.tasks]
+ results = [task.result for task in self.planner.plan.tasks]
+ return {"codes": codes, "results": results}
+
+ def save_state(self, static_save=False):
+ """
+ attribute:
+ state_saved - the state has been saved
+ input:
+ static_save - saving the state without changing the state_saved flag - used when a new role is created
+ """
+ if self.state_saved and not static_save:
+ return
+ if not static_save:
+ self.state_saved = True
+ mcts_logger.log("MCTS", f"Saving state at task {self.start_task_id}")
+ else:
+ mcts_logger.log("MCTS", "Static Saving")
+ stg_path = self.role_dir
+ name = self.get_node_name()
+ role_path = os.path.join(stg_path, f"{name}.json")
+ # save state as json file
+ write_json_file(role_path, self.model_dump())
+
+ def remap_tasks(self):
+ self.planner.plan.tasks = [
+ self.planner.plan.task_map[task_id] for task_id in sorted(self.planner.plan.task_map.keys())
+ ]
+
+ @async_timeout()
+ async def run(self, with_message=None) -> Message | None:
+ """Observe, and think and act based on the results of the observation"""
+ if with_message == "continue":
+ mcts_logger.info("Continue to run")
+ self.rc.working_memory.clear()
+ self.working_memory.clear()
+ rsp = await self.react()
+ self.set_todo(None)
+ self.publish_message(rsp)
+ return rsp
+ return await super().run(with_message)
diff --git a/metagpt/ext/sela/insights/fixed_insights.json b/metagpt/ext/sela/insights/fixed_insights.json
new file mode 100644
index 000000000..4f42b9db1
--- /dev/null
+++ b/metagpt/ext/sela/insights/fixed_insights.json
@@ -0,0 +1,22 @@
+[
+{
+ "Analysis": "Use early stopping, hyperparameter tuning, and cross-validation to avoid overfitting and improve robustness of the model.",
+ "Category": "Model Training",
+ "task_id": 4
+},
+{
+ "Analysis": "use k-fold bagging and early stopping",
+ "Category": "Model Training",
+ "task_id": 4
+},
+{
+ "Analysis": "To avoid overfitting, train a weighted ensemble model such as StackingClassifier or StackingRegressor; You could do some quick model prototyping to see which models work best and then use them in the ensemble.",
+ "Category": "Model Training",
+ "task_id": 4
+},
+{
+ "Analysis": "Please use autogluon for model training with presets='medium_quality', time_limit=None, give dev dataset to tuning_data, and use right eval_metric.",
+ "Category": "Model Training",
+ "task_id": 4
+}
+]
\ No newline at end of file
diff --git a/metagpt/ext/sela/insights/instruction_generator.py b/metagpt/ext/sela/insights/instruction_generator.py
new file mode 100644
index 000000000..d5d24c74d
--- /dev/null
+++ b/metagpt/ext/sela/insights/instruction_generator.py
@@ -0,0 +1,169 @@
+import json
+import os
+import random
+from difflib import SequenceMatcher
+
+from metagpt.ext.sela.insights.solution_designer import SolutionDesigner
+from metagpt.ext.sela.utils import clean_json_from_rsp, load_data_config, mcts_logger
+from metagpt.llm import LLM
+from metagpt.schema import Message
+
+REFLECTION_SYSTEM_MSG = "As a Kaggle Grandmaster competing in a challenge, your task is to suggest potential evolutionary improvements that could enhance the performance of the baseline code."
+
+CHANGE_INSTRUCTION = """
+# Original instruction
+{instruction}
+
+# Insights
+{insights}
+
+Rewrite the original instruction according to the insights
+(If the original instruction involves splitting the data, ensure that your insights are integrated with the data split instructions,
+rather than replacing them.)
+
+# Expected Output Hard Format
+```json
+{{
+ "Original Instruction": "original instruction",
+ "New Instruction": "new instruction"
+}}
+```
+"""
+
+DATA_CONFIG = load_data_config()
+
+
+class InstructionGenerator:
+ data_config = DATA_CONFIG
+
+ def __init__(self, state, use_fixed_insights, from_scratch):
+ self.state = state
+ self.file_path = state["exp_pool_path"]
+ if state["custom_dataset_dir"]:
+ with open(f"{state['custom_dataset_dir']}/description.md", "r", encoding="utf-8") as file:
+ self.dataset_info = file.read()
+ else:
+ dataset_info_path = (
+ f"{self.data_config['datasets_dir']}/{state['dataset_config']['dataset']}/dataset_info.json"
+ )
+ with open(dataset_info_path, "r") as file:
+ self.dataset_info = json.load(file)
+ self.use_fixed_insights = use_fixed_insights
+ self.proposer = SolutionDesigner()
+ if self.file_path is None:
+ self.from_scratch = True
+ else:
+ self.from_scratch = from_scratch
+
+ async def initialize(self):
+ if self.from_scratch:
+ self.insight_pool = await self.generate_solutions_from_scratch(self.dataset_info, self.state["task"])
+ else:
+ self.insight_pool = self.load_insight_pool(self.file_path, self.use_fixed_insights)
+
+ @staticmethod
+ def load_json_data(json_dir):
+ with open(json_dir, "r") as file:
+ json_data = json.load(file)
+ return json_data
+
+ @staticmethod
+ def _random_sample(analysis, num_samples):
+ return random.sample(analysis, num_samples)
+
+ @staticmethod
+ def sample_instruction_set(data):
+ data_dict = {}
+ for item in data:
+ task_id = item["task_id"]
+ if task_id not in data_dict:
+ data_dict[task_id] = []
+ data_dict[task_id].append(item)
+ instruction_set = []
+ for task_id in sorted(data_dict.keys()):
+ instruction_set.append(random.choice(data_dict[task_id]))
+ return instruction_set
+
+ @staticmethod
+ def format_output(rsp):
+ rsp_list = []
+ new_data = []
+ rsp_list.append(rsp)
+ for item in rsp_list:
+ item_dict = json.loads(item)
+ data = {
+ "Insights": item_dict,
+ }
+ new_data.append(data)
+ return new_data
+
+ @staticmethod
+ def load_insight_pool(file_path, use_fixed_insights, task_id=None):
+ data = InstructionGenerator.load_json_data(file_path)
+ if use_fixed_insights:
+ current_directory = os.path.dirname(__file__)
+ fixed_insights = InstructionGenerator.load_json_data(f"{current_directory}/fixed_insights.json")
+ data.extend(fixed_insights)
+ for item in data:
+ if "task_id" not in item:
+ raise ValueError("task_id is not found in the insight_pool")
+
+ if task_id:
+ data = [item for item in data if int(item["task_id"]) == int(task_id)]
+ return data
+
+ async def generate_new_instructions(self, task_id, original_instruction, max_num, ext_info=None):
+ data = self.insight_pool
+ new_instructions = []
+ if len(data) == 0:
+ mcts_logger.log("MCTS", f"No insights available for task {task_id}")
+ # return [original_instruction] # Return the original instruction if no insights are available
+ for i in range(max_num):
+ if len(data) == 0:
+ insights = "No insights available"
+ else:
+ item = data[i]
+ insights = item["Analysis"]
+ new_instruction = await InstructionGenerator.generate_new_instruction(
+ original_instruction, insights, ext_info
+ )
+ new_instructions.append(new_instruction)
+ return new_instructions
+
+ async def propose_new_insights(self, solution, score):
+ new_insights = await self.proposer.propose_insights(solution, score)
+ added_insights = self.add_insight(new_insights)
+ return added_insights
+
+ async def generate_solutions_from_scratch(self, dataset_info, dataset_name):
+ insight_pool = await self.proposer.generate_solutions(dataset_info, dataset_name, save_analysis_pool=False)
+ return insight_pool
+
+ def add_insight(self, new_insights):
+ added_insights = []
+ for new_insight in new_insights:
+ if not self.is_similar_to_existing(new_insight):
+ added_insights.append(new_insight)
+ self.insight_pool.append(new_insight)
+ return added_insights
+
+ def is_similar_to_existing(self, new_insight, similarity_threshold=0.8):
+ for existing_insight in self.insight_pool:
+ similarity = self.calculate_similarity(new_insight["Analysis"], existing_insight["Analysis"])
+ if similarity > similarity_threshold:
+ return True
+ return False
+
+ @staticmethod
+ def calculate_similarity(text1, text2):
+ return SequenceMatcher(None, text1, text2).ratio()
+
+ @staticmethod
+ async def generate_new_instruction(original_instruction, insights, ext_info):
+ prompt = CHANGE_INSTRUCTION.format(instruction=original_instruction, insights=insights)
+ llm = LLM()
+ context = llm.format_msg([Message(content=prompt, role="user")])
+ llm_response = await llm.aask(context, system_msgs=[REFLECTION_SYSTEM_MSG])
+ rsp = clean_json_from_rsp(llm_response)
+ new_instruction = json.loads(rsp)["New Instruction"]
+ return new_instruction
diff --git a/metagpt/ext/sela/insights/solution_designer.py b/metagpt/ext/sela/insights/solution_designer.py
new file mode 100644
index 000000000..1b61c2141
--- /dev/null
+++ b/metagpt/ext/sela/insights/solution_designer.py
@@ -0,0 +1,183 @@
+import json
+
+from metagpt.ext.sela.utils import clean_json_from_rsp, load_data_config
+from metagpt.llm import LLM
+
+DATA_CONFIG = load_data_config()
+
+
+DATASET_DESCRIPTION_SELA_PROMPT = """
+# Dataset Description
+{dataset}
+
+# Dataset Metadata
+{metadata}
+
+# Dataset Head
+{head}
+"""
+
+DATASET_DESCRIPTION_CUSTOM_PROMPT = """
+# Dataset Description
+{dataset_description}
+"""
+
+DATASET_INSIGHT_PROMPT = """
+{description}
+
+# Instruction
+Propose insights to help improve the performance of the model on this dataset.
+The insights should be proposed based on the dataset description with different task types.
+Each task type should have at least 5 insights.
+Make sure each method is diverse enough and can be implemented separately.
+Be specific about models' choices, ensemble and tuning techniques, and preprocessing & feature engineering techniques.
+Your model choices should be advanced enough to be helpful.
+
+# Format
+```json
+[
+ {{
+ "task_type": "EDA",
+ "insights": [
+ "insight1",
+ "insight2",
+ "insight3",
+ ...
+ "insightN"
+ ]
+ }},
+ {{
+ "task_type": "Data Preprocessing",
+ "insights": [
+ "insight1",
+ "insight2",
+ "insight3",
+ ...
+ "insightN"
+ ]
+ }},
+ {{
+ "task_type": "Feature Engineering",
+ "insights": [
+ "insight1",
+ "insight2",
+ "insight3",
+ ...
+ "insightN"
+ ]
+ }},
+ {{
+ "task_type": "Model Training",
+ "insights": [
+ "insight1",
+ "insight2",
+ "insight3",
+ ...
+ "insightN"
+ ]
+ }}
+]
+```
+"""
+
+
+INSIGHT_PROPOSAL_PROMPT = """
+You are an AI assistant tasked with analyzing a machine learning solution and proposing new insights to improve its performance. Given the current solution code and development score, suggest innovative approaches to enhance the model.
+
+Current Solution Code:
+{solution_code}
+
+Development Score: {dev_score}
+
+Based on this information, propose 3-5 new insights across different aspects of the machine learning pipeline (Data Preprocessing, Feature Engineering, and Model Training). Your insights should be specific, actionable, and have the potential to improve the model's performance.
+
+Please format your response as a JSON array with the following structure:
+[
+
+ {{
+ "task_type": "Data Preprocessing",
+ "insights": [
+ "insight1",
+ "insight2"
+ ]
+ }},
+ {{
+ "task_type": "Feature Engineering",
+ "insights": [
+ "insight1",
+ "insight2"
+ ]
+ }},
+ {{
+ "task_type": "Model Training",
+ "insights": [
+ "insight1",
+ "insight2"
+ ]
+ }}
+]
+"""
+
+
+KEY_DATASET_FEATURES = [
+ "NumberOfClasses",
+ "NumberOfFeatures",
+ "NumberOfInstances",
+ "NumberOfInstancesWithMissingValues",
+ "NumberOfMissingValues",
+ "NumberOfNumericFeatures",
+ "NumberOfSymbolicFeatures",
+]
+
+TASK_TO_ID = {"EDA": 1, "Data Preprocessing": 2, "Feature Engineering": 3, "Model Training": 4, "Model Evaluation": 5}
+
+
+class SolutionDesigner:
+ data_dir: str = DATA_CONFIG["datasets_dir"]
+
+ async def generate_solutions(self, dataset_info, dataset_name, save_analysis_pool=True):
+ llm = LLM()
+ if type(dataset_info) == dict:
+ description_prompt = DATASET_DESCRIPTION_SELA_PROMPT.format(
+ dataset=dataset_info["description"],
+ metadata=self.metadata_builder(dataset_info["metadata"]),
+ head=dataset_info["df_head"],
+ )
+ else:
+ description_prompt = DATASET_DESCRIPTION_CUSTOM_PROMPT.format(dataset_description=dataset_info)
+ context = DATASET_INSIGHT_PROMPT.format(description=description_prompt)
+ rsp = await llm.aask(context)
+ rsp = clean_json_from_rsp(rsp)
+ analysis_pool = self.process_analysis_pool(json.loads(rsp))
+ if save_analysis_pool:
+ dataset_path = f"{self.data_dir}/{dataset_name}"
+ self.save_analysis_pool(dataset_path, analysis_pool)
+ return analysis_pool
+
+ async def propose_new_insights(self, solution, score):
+ llm = LLM()
+ context = INSIGHT_PROPOSAL_PROMPT.format(solution_code=solution, dev_score=score)
+ rsp = await llm.aask(context)
+ rsp = clean_json_from_rsp(rsp)
+ new_insights = self.process_analysis_pool(json.loads(rsp))
+ return new_insights
+
+ def process_analysis_pool(self, insights_rsp):
+ analysis_pool = []
+ for task_type_insights in insights_rsp:
+ task_type = task_type_insights["task_type"]
+ for insight in task_type_insights["insights"]:
+ analysis_pool.append({"Analysis": insight, "Category": task_type, "task_id": TASK_TO_ID[task_type]})
+ return analysis_pool
+
+ def metadata_builder(self, qualities):
+ metadata = {}
+ for key in KEY_DATASET_FEATURES:
+ metadata[key] = qualities.get(key, "N/A")
+ metadata_text = json.dumps(metadata, indent=4)
+ return metadata_text
+
+ def save_analysis_pool(self, dataset_path, analysis_pool):
+ fpath = f"{dataset_path}/ds_analysis_pool.json"
+ with open(fpath, "w") as file:
+ json.dump(analysis_pool, file, indent=4)
diff --git a/metagpt/ext/sela/requirements.txt b/metagpt/ext/sela/requirements.txt
new file mode 100644
index 000000000..e85818bbe
--- /dev/null
+++ b/metagpt/ext/sela/requirements.txt
@@ -0,0 +1,6 @@
+# expo
+openml==0.14.2
+# ml module to run in DI
+xgboost
+catboost
+lightgbm
diff --git a/metagpt/ext/sela/run_experiment.py b/metagpt/ext/sela/run_experiment.py
new file mode 100644
index 000000000..32130a6fb
--- /dev/null
+++ b/metagpt/ext/sela/run_experiment.py
@@ -0,0 +1,99 @@
+import argparse
+import asyncio
+
+from metagpt.ext.sela.data.custom_task import get_mle_is_lower_better, get_mle_task_id
+from metagpt.ext.sela.runner.autogluon import GluonRunner
+from metagpt.ext.sela.runner.autosklearn import AutoSklearnRunner
+from metagpt.ext.sela.runner.custom import CustomRunner
+from metagpt.ext.sela.runner.mcts import MCTSRunner
+from metagpt.ext.sela.runner.random_search import RandomSearchRunner
+from metagpt.ext.sela.runner.runner import Runner
+
+
+def get_args(cmd=True):
+ parser = argparse.ArgumentParser()
+ parser.add_argument("--name", type=str, default="")
+ parser.add_argument(
+ "--exp_mode",
+ type=str,
+ default="mcts",
+ choices=["mcts", "rs", "base", "custom", "greedy", "autogluon", "random", "autosklearn"],
+ )
+ parser.add_argument("--role_timeout", type=int, default=1000)
+ get_di_args(parser)
+ get_mcts_args(parser)
+ get_rs_exp_args(parser)
+ if cmd:
+ args = parser.parse_args()
+ else:
+ args = parser.parse_args("")
+
+ if args.custom_dataset_dir:
+ args.external_eval = False
+ args.eval_func = "mlebench"
+ args.from_scratch = True
+ args.task = get_mle_task_id(args.custom_dataset_dir)
+ args.low_is_better = get_mle_is_lower_better(args.task)
+ return args
+
+
+def get_mcts_args(parser):
+ parser.add_argument("--load_tree", dest="load_tree", action="store_true")
+ parser.add_argument("--no_load_tree", dest="load_tree", action="store_false")
+ parser.set_defaults(load_tree=False)
+ parser.add_argument("--rollouts", type=int, default=5)
+ parser.add_argument("--use_fixed_insights", dest="use_fixed_insights", action="store_true")
+ parser.set_defaults(use_fixed_insights=False)
+ parser.add_argument("--start_task_id", type=int, default=2)
+ parser.add_argument(
+ "--from_scratch", dest="from_scratch", action="store_true", help="Generate solutions from scratch"
+ )
+ parser.set_defaults(from_scratch=False)
+ parser.add_argument("--no_external_eval", dest="external_eval", action="store_false")
+ parser.set_defaults(external_eval=True)
+ parser.add_argument("--eval_func", type=str, default="sela", choices=["sela", "mlebench"])
+ parser.add_argument("--custom_dataset_dir", type=str, default=None)
+ parser.add_argument("--max_depth", type=int, default=4)
+
+
+def get_rs_exp_args(parser):
+ parser.add_argument("--rs_mode", type=str, default="single", choices=["single", "set"])
+ parser.add_argument("--is_multimodal", action="store_true", help="Specify if the model is multi-modal")
+
+
+def get_di_args(parser):
+ parser.add_argument("--task", type=str, default="titanic")
+ parser.add_argument("--low_is_better", dest="low_is_better", action="store_true")
+ parser.set_defaults(low_is_better=False)
+ parser.add_argument("--reflection", dest="reflection", action="store_true")
+ parser.add_argument("--no_reflection", dest="reflection", action="store_false")
+ parser.add_argument("--num_experiments", type=int, default=1)
+ parser.add_argument("--special_instruction", type=str, default=None, choices=["ag", "stacking", "text", "image"])
+ parser.set_defaults(reflection=True)
+
+
+async def main(args):
+ if args.exp_mode == "mcts":
+ runner = MCTSRunner(args)
+ elif args.exp_mode == "greedy":
+ runner = MCTSRunner(args, tree_mode="greedy")
+ elif args.exp_mode == "random":
+ runner = MCTSRunner(args, tree_mode="random")
+ elif args.exp_mode == "rs":
+ runner = RandomSearchRunner(args)
+ elif args.exp_mode == "base":
+ runner = Runner(args)
+ elif args.exp_mode == "autogluon":
+ runner = GluonRunner(args)
+ elif args.exp_mode == "custom":
+ runner = CustomRunner(args)
+ elif args.exp_mode == "autosklearn":
+ runner = AutoSklearnRunner(args)
+ else:
+ raise ValueError(f"Invalid exp_mode: {args.exp_mode}")
+ await runner.run_experiment()
+
+
+if __name__ == "__main__":
+ args = get_args()
+ asyncio.run(main(args))
diff --git a/metagpt/ext/sela/runner/README.md b/metagpt/ext/sela/runner/README.md
new file mode 100644
index 000000000..4867aa4f0
--- /dev/null
+++ b/metagpt/ext/sela/runner/README.md
@@ -0,0 +1,168 @@
+# SELA: Tree-Search Enhanced LLM Agents for Automated Machine Learning
+
+This document provides instructions for running baseline models. To start with, ensure that you prepare the datasets as instructed in `sela/README.md`.
+
+## Baselines
+
+### 1. AIDE
+
+#### Setup
+
+We use the AIDE version from September 30, 2024. Clone the repository and check out the specified commit:
+
+```bash
+git clone https://github.com/WecoAI/aideml.git
+git checkout 77953247ea0a5dc1bd502dd10939dd6d7fdcc5cc
+```
+
+
+Modify `aideml/aide/utils/config.yaml` to set the following parameters:
+
+```yaml
+# agent hyperparams
+agent:
+ steps: 10 # Number of improvement iterations
+ k_fold_validation: 1 # Set to 1 to disable cross-validation
+ code:
+ model: deepseek-coder
+ temp: 0.5
+ feedback:
+ model: deepseek-coder
+ temp: 0.5
+ search:
+ max_debug_depth: 3
+ debug_prob: 0.5
+ num_drafts: 5
+```
+
+Update your OpenAI API credentials in the environment:
+
+```bash
+export OPENAI_API_KEY="your api key"
+export OPENAI_BASE_URL="your own url"
+```
+
+Modify `aideml/aide/backend/__init__.py` (line 30 and below):
+
+```python
+model_kwargs = model_kwargs | {
+ "model": model,
+ "temperature": temperature,
+ "max_tokens": max_tokens,
+ }
+if "claude-" in model:
+ query_func = backend_anthropic.query
+else:
+ query_func = backend_openai.query
+```
+
+Since Deepseek V2.5 no longer supports system messages using function calls, modify `aideml/aide/agent.py` (line 312):
+
+```python
+response = cast(
+ dict,
+ query(
+ system_message=None,
+ user_message=prompt,
+ func_spec=review_func_spec,
+ model=self.acfg.feedback.model,
+ temperature=self.acfg.feedback.temp,
+ ),
+)
+```
+
+Finally, install AIDE:
+
+```bash
+cd aideml
+pip install -e .
+```
+
+#### Run
+
+Execute the following script to generate results. A `log` folder (containing experimental configurations) and a `workspace` folder (storing final results) will be created:
+
+```bash
+python runner/aide.py
+```
+
+---
+
+### 2. Autogluon
+
+#### Setup
+
+Install Autogluon:
+
+```bash
+pip install -U pip
+pip install -U setuptools wheel
+pip install autogluon==1.1.1
+```
+
+#### Run
+
+For Tabular data:
+
+```bash
+python run_experiment.py --exp_mode autogluon --task {task_name}
+```
+
+For Multimodal data:
+
+```bash
+python run_experiment.py --exp_mode autogluon --task {task_name} --is_multimodal
+```
+
+Replace `{task_name}` with the specific task you want to run.
+
+---
+
+### 3. AutoSklearn
+
+**Note:**
+AutoSklearn requires:
+- Linux operating system (e.g., Ubuntu)
+- Python (>=3.7)
+- C++ compiler (with C++11 support)
+
+If installing on a system without wheel files for the `pyrfr` package, you also need:
+
+- [SWIG](https://www.swig.org/survey.html)
+
+Refer to the [Windows/macOS compatibility](https://automl.github.io/auto-sklearn/master/installation.html#windows-macos-compatibility) section for further details.
+
+#### Setup
+
+Install AutoSklearn:
+
+```bash
+pip install auto-sklearn==0.15.0
+```
+
+#### Run
+
+Execute the following command for the Titanic task:
+
+```bash
+python run_experiment.py --exp_mode autosklearn --task titanic
+```
+
+---
+
+### 4. Base Data Interpreter
+
+Run the following command for the Titanic task:
+
+```bash
+python run_experiment.py --exp_mode base --task titanic --num_experiments 10
+```
+
+---
+
+### 5. Custom Baselines
+
+To run additional baselines:
+
+- Each baseline must produce `dev_predictions.csv` and `test_predictions.csv` with a `target` column.
+- Use the `evaluate_score` function for evaluation.
\ No newline at end of file
diff --git a/metagpt/ext/sela/runner/__init__.py b/metagpt/ext/sela/runner/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/metagpt/ext/sela/runner/aide.py b/metagpt/ext/sela/runner/aide.py
new file mode 100644
index 000000000..50fae94c1
--- /dev/null
+++ b/metagpt/ext/sela/runner/aide.py
@@ -0,0 +1,35 @@
+import os
+import time
+
+import aide
+
+os.environ["OPENAI_API_KEY"] = "sk-xxx"
+os.environ["OPENAI_BASE_URL"] = "your url"
+
+start_time = time.time()
+
+data_dir = "xxx/data/titanic"
+
+goal = f"""
+# User requirement
+({data_dir}, 'This is a 04_titanic dataset. Your goal is to predict the target column `Survived`.\nPerform data analysis, data preprocessing, feature engineering, and modeling to predict the target. \nReport f1 on the eval data. Do not plot or make any visualizations.\n')
+
+# Data dir
+training (with labels): train.csv
+testing (without labels): test.csv
+dataset description: dataset_info.json (You can use this file to get additional information about the dataset)"""
+
+exp = aide.Experiment(
+ data_dir=data_dir, # replace this with your own directory
+ goal=goal,
+ eval="f1", # replace with your own evaluation metric
+)
+
+best_solution = exp.run(steps=10)
+
+print(f"Best solution has validation metric: {best_solution.valid_metric}")
+print(f"Best solution code: {best_solution.code}")
+end_time = time.time()
+execution_time = end_time - start_time
+
+print(f"run time : {execution_time} seconds")
diff --git a/metagpt/ext/sela/runner/autogluon.py b/metagpt/ext/sela/runner/autogluon.py
new file mode 100644
index 000000000..48737da04
--- /dev/null
+++ b/metagpt/ext/sela/runner/autogluon.py
@@ -0,0 +1,128 @@
+import os
+from datetime import datetime
+
+import pandas as pd
+
+from metagpt.ext.sela.runner.custom import CustomRunner
+
+
+class AGRunner:
+ def __init__(self, state=None):
+ self.state = state
+ self.datasets = self.state["datasets_dir"]
+
+ def run(self):
+ from autogluon.tabular import TabularDataset, TabularPredictor
+
+ train_path = self.datasets["train"]
+ dev_path = self.datasets["dev"]
+ dev_wo_target_path = self.datasets["dev_wo_target"]
+ test_wo_target_path = self.datasets["test_wo_target"]
+ target_col = self.state["dataset_config"]["target_col"]
+ train_data = TabularDataset(train_path)
+ dev_data = TabularDataset(dev_path)
+ dev_wo_target_data = TabularDataset(dev_wo_target_path)
+ test_data = TabularDataset(test_wo_target_path)
+ eval_metric = self.state["dataset_config"]["metric"].replace(" ", "_")
+ predictor = TabularPredictor(
+ label=target_col,
+ eval_metric=eval_metric,
+ path="AutogluonModels/ag-{}-{}".format(self.state["task"], datetime.now().strftime("%y%m%d_%H%M")),
+ ).fit(train_data=train_data, tuning_data=dev_data, num_gpus=1)
+ dev_preds = predictor.predict(dev_wo_target_data)
+ test_preds = predictor.predict(test_data)
+ return {"test_preds": test_preds, "dev_preds": dev_preds}
+
+ def run_multimodal(self):
+ from autogluon.multimodal import MultiModalPredictor
+
+ target_col = self.state["dataset_config"]["target_col"]
+ train_path = self.datasets["train"]
+ dev_path = self.datasets["dev"]
+ dev_wo_target_path = self.datasets["dev_wo_target"] # Updated variable name
+ test_wo_target_path = self.datasets["test_wo_target"]
+ eval_metric = self.state["dataset_config"]["metric"].replace(" ", "_")
+
+ # Load the datasets
+ train_data, dev_data, dev_wo_target_data, test_data = self.load_split_dataset(
+ train_path, dev_path, dev_wo_target_path, test_wo_target_path
+ )
+
+ # Create and fit the predictor
+ predictor = MultiModalPredictor(
+ label=target_col,
+ eval_metric=eval_metric,
+ path="AutogluonModels/ag-{}-{}".format(self.state["task"], datetime.now().strftime("%y%m%d_%H%M")),
+ ).fit(train_data=train_data, tuning_data=dev_data)
+
+ # Make predictions on dev and test datasets
+ dev_preds = predictor.predict(dev_wo_target_data)
+ test_preds = predictor.predict(test_data)
+
+ # Return predictions for dev and test datasets
+ return {"dev_preds": dev_preds, "test_preds": test_preds}
+
+ def load_split_dataset(self, train_path, dev_path, dev_wo_target_path, test_wo_target_path):
+ """
+ Loads training, dev, and test datasets from given file paths
+
+ Args:
+ train_path (str): Path to the training dataset.
+ dev_path (str): Path to the dev dataset with target labels.
+ dev_wo_target_path (str): Path to the dev dataset without target labels.
+ test_wo_target_path (str): Path to the test dataset without target labels.
+
+ Returns:
+ train_data (pd.DataFrame): Loaded training dataset with updated image paths.
+ dev_data (pd.DataFrame): Loaded dev dataset with updated image paths.
+ dev_wo_target_data (pd.DataFrame): Loaded dev dataset without target labels and updated image paths.
+ test_data (pd.DataFrame): Loaded test dataset with updated image paths.
+ """
+
+ # Define the root path to append
+ root_folder = os.path.join("F:/Download/Dataset/", self.state["task"])
+
+ # Load the datasets
+ train_data = pd.read_csv(train_path)
+ dev_data = pd.read_csv(dev_path) # Load dev dataset with target labels
+ dev_wo_target_data = pd.read_csv(dev_wo_target_path) # Load dev dataset without target labels
+ test_data = pd.read_csv(test_wo_target_path)
+
+ # Get the name of the first column (assuming it's the image path column)
+ image_column = train_data.columns[0]
+
+ # Append root folder path to the image column in each dataset
+ train_data[image_column] = train_data[image_column].apply(lambda x: os.path.join(root_folder, x))
+ dev_data[image_column] = dev_data[image_column].apply(lambda x: os.path.join(root_folder, x))
+ dev_wo_target_data[image_column] = dev_wo_target_data[image_column].apply(
+ lambda x: os.path.join(root_folder, x)
+ )
+ test_data[image_column] = test_data[image_column].apply(lambda x: os.path.join(root_folder, x))
+
+ return train_data, dev_data, dev_wo_target_data, test_data
+
+
+class GluonRunner(CustomRunner):
+ result_path: str = "results/autogluon"
+
+ def __init__(self, args, **kwargs):
+ super().__init__(args, **kwargs)
+ self.framework = AGRunner(self.state)
+ self.is_multimodal = args.is_multimodal if hasattr(args, "is_multimodal") else False
+
+ async def run_experiment(self):
+ if not self.is_multimodal:
+ result = self.framework.run()
+ else:
+ result = self.framework.run_multimodal()
+
+ assert result is not None
+ user_requirement = self.state["requirement"]
+ dev_preds = result["dev_preds"]
+ test_preds = result["test_preds"]
+ score_dict = {
+ "dev_score": self.evaluate_predictions(dev_preds, "dev"),
+ "test_score": self.evaluate_predictions(test_preds, "test"),
+ }
+ results = [0, {"score_dict": score_dict, "user_requirement": user_requirement, "args": vars(self.args)}]
+ self.save_result(results)
diff --git a/metagpt/ext/sela/runner/autosklearn.py b/metagpt/ext/sela/runner/autosklearn.py
new file mode 100644
index 000000000..7d0eb364e
--- /dev/null
+++ b/metagpt/ext/sela/runner/autosklearn.py
@@ -0,0 +1,96 @@
+from datetime import datetime
+from functools import partial
+
+import pandas as pd
+
+from metagpt.ext.sela.evaluation.evaluation import evaluate_score
+from metagpt.ext.sela.runner.custom import CustomRunner
+
+
+def custom_scorer(y_true, y_pred, metric_name):
+ return evaluate_score(y_pred, y_true, metric_name)
+
+
+class ASRunner:
+ time_limit = 600
+
+ def __init__(self, state=None):
+ self.state = state
+ self.datasets = self.state["datasets_dir"]
+
+ def create_autosklearn_scorer(self, metric_name):
+ from autosklearn.metrics import make_scorer
+
+ return make_scorer(name=metric_name, score_func=partial(custom_scorer, metric_name=metric_name))
+
+ def run(self):
+ import autosklearn.classification
+ import autosklearn.regression
+
+ train_path = self.datasets["train"]
+ dev_wo_target_path = self.datasets["dev_wo_target"]
+ test_wo_target_path = self.datasets["test_wo_target"]
+ target_col = self.state["dataset_config"]["target_col"]
+
+ train_data = pd.read_csv(train_path)
+ dev_data = pd.read_csv(dev_wo_target_path)
+ test_data = pd.read_csv(test_wo_target_path)
+ eval_metric = self.state["dataset_config"]["metric"]
+ X_train = train_data.drop(columns=[target_col])
+ y_train = train_data[target_col]
+
+ if eval_metric == "rmse":
+ automl = autosklearn.regression.AutoSklearnRegressor(
+ time_left_for_this_task=self.time_limit,
+ metric=self.create_autosklearn_scorer(eval_metric),
+ memory_limit=8192,
+ tmp_folder="AutosklearnModels/as-{}-{}".format(
+ self.state["task"], datetime.now().strftime("%y%m%d_%H%M")
+ ),
+ n_jobs=-1,
+ )
+ elif eval_metric in ["f1", "f1 weighted"]:
+ automl = autosklearn.classification.AutoSklearnClassifier(
+ time_left_for_this_task=self.time_limit,
+ metric=self.create_autosklearn_scorer(eval_metric),
+ memory_limit=8192,
+ tmp_folder="AutosklearnModels/as-{}-{}".format(
+ self.state["task"], datetime.now().strftime("%y%m%d_%H%M")
+ ),
+ n_jobs=-1,
+ )
+ else:
+ raise ValueError(f"Unsupported metric: {eval_metric}")
+ automl.fit(X_train, y_train)
+
+ dev_preds = automl.predict(dev_data)
+ test_preds = automl.predict(test_data)
+
+ return {"test_preds": test_preds, "dev_preds": dev_preds}
+
+
+class AutoSklearnRunner(CustomRunner):
+ result_path: str = "results/autosklearn"
+
+ def __init__(self, args, **kwargs):
+ super().__init__(args, **kwargs)
+ self.framework = ASRunner(self.state)
+
+ async def run_experiment(self):
+ result = self.framework.run()
+ user_requirement = self.state["requirement"]
+ dev_preds = result["dev_preds"]
+ test_preds = result["test_preds"]
+ score_dict = {
+ "dev_score": self.evaluate_predictions(dev_preds, "dev"),
+ "test_score": self.evaluate_predictions(test_preds, "test"),
+ }
+ results = [
+ 0,
+ {
+ "score_dict": score_dict,
+ "user_requirement": user_requirement,
+ "args": vars(self.args),
+ },
+ ]
+ self.save_result(results)
diff --git a/metagpt/ext/sela/runner/custom.py b/metagpt/ext/sela/runner/custom.py
new file mode 100644
index 000000000..e9a8ee276
--- /dev/null
+++ b/metagpt/ext/sela/runner/custom.py
@@ -0,0 +1,62 @@
+import os
+
+import pandas as pd
+
+from metagpt.ext.sela.evaluation.evaluation import evaluate_score
+from metagpt.ext.sela.runner.runner import Runner
+from metagpt.ext.sela.search.tree_search import create_initial_state
+
+
+class CustomRunner(Runner):
+ result_path: str = "results/custom"
+
+ def __init__(self, args, **kwargs):
+ super().__init__(args, **kwargs)
+ self.framework = kwargs.get("framework", None) # todo
+ self.task = kwargs.get("task", self.args.task)
+ self.low_is_better = kwargs.get("low_is_better", self.args.low_is_better)
+ self.name = kwargs.get("name", "")
+ self.result_path = f"results/custom_{self.name}"
+ self.state = create_initial_state(
+ self.task,
+ start_task_id=1,
+ data_config=self.data_config,
+ args=self.args,
+ )
+
+ def run_experiment(self):
+ user_requirement = self.state["requirement"]
+ preds = self.framework.run(user_requirement)
+ test_preds = preds["test_preds"]
+ dev_preds = preds["dev_preds"]
+ score_dict = {
+ "dev_score": self.evaluate_predictions(dev_preds, "dev"),
+ "test_score": self.evaluate_predictions(test_preds, "test"),
+ }
+ results = {"score_dict": score_dict, "user_requirement": user_requirement, "args": vars(self.args)}
+ self.save_result(results)
+
+ def evaluate_pred_files(self, dev_pred_path, test_pred_path):
+ dev_preds = pd.read_csv(dev_pred_path)["target"]
+ test_preds = pd.read_csv(test_pred_path)["target"]
+ score_dict = {
+ "dev_score": self.evaluate_score(dev_preds, "dev"),
+ "test_score": self.evaluate_score(test_preds, "test"),
+ }
+ return score_dict
+
+ def evaluate_predictions(self, preds, split):
+ metric = self.state["dataset_config"]["metric"]
+ gt_path = os.path.join(self.state["datasets_dir"][f"{split}_target"])
+ gt = pd.read_csv(gt_path)["target"]
+ score = evaluate_score(preds, gt, metric)
+ return score
+
+ def load_datasets(self):
+ train_path = self.state["datasets_dir"]["train"]
+ dev_path = self.state["datasets_dir"]["dev"]
+ test_path = self.state["datasets_dir"]["test"]
+ train = pd.read_csv(train_path)
+ dev = pd.read_csv(dev_path)
+ test = pd.read_csv(test_path)
+ return train, dev, test
diff --git a/metagpt/ext/sela/runner/mcts.py b/metagpt/ext/sela/runner/mcts.py
new file mode 100644
index 000000000..8b6c14100
--- /dev/null
+++ b/metagpt/ext/sela/runner/mcts.py
@@ -0,0 +1,80 @@
+import shutil
+
+from metagpt.ext.sela.evaluation.evaluation import (
+ node_evaluate_score_mlebench,
+ node_evaluate_score_sela,
+)
+from metagpt.ext.sela.evaluation.visualize_mcts import get_tree_text
+from metagpt.ext.sela.runner.runner import Runner
+from metagpt.ext.sela.search.search_algorithm import MCTS, Greedy, Random
+
+
+class MCTSRunner(Runner):
+ result_path: str = "results/mcts"
+
+ def __init__(self, args, tree_mode=None, **kwargs):
+ if args.special_instruction == "image":
+ self.start_task_id = 1 # start from datapreprocessing if it is image task
+ else:
+ self.start_task_id = args.start_task_id
+
+ if args.eval_func == "sela":
+ self.eval_func = node_evaluate_score_sela
+ elif args.eval_func == "mlebench":
+ self.eval_func = node_evaluate_score_mlebench
+
+ super().__init__(args, **kwargs)
+ self.tree_mode = tree_mode
+
+ async def run_experiment(self):
+ use_fixed_insights = self.args.use_fixed_insights
+ depth = self.args.max_depth
+ if self.tree_mode == "greedy":
+ mcts = Greedy(root_node=None, max_depth=depth, use_fixed_insights=use_fixed_insights)
+ elif self.tree_mode == "random":
+ mcts = Random(root_node=None, max_depth=depth, use_fixed_insights=use_fixed_insights)
+ else:
+ mcts = MCTS(root_node=None, max_depth=depth, use_fixed_insights=use_fixed_insights)
+ best_nodes = await mcts.search(state=self.state, args=self.args)
+ best_node = best_nodes["global_best"]
+ dev_best_node = best_nodes["dev_best"]
+ score_dict = best_nodes["scores"]
+ additional_scores = {"grader": self.eval_func(dev_best_node)}
+
+ text, num_generated_codes = get_tree_text(mcts.root_node)
+ text += f"Generated {num_generated_codes} unique codes.\n"
+ text += f"Best node: {best_node.id}, score: {best_node.raw_reward}\n"
+ text += f"Dev best node: {dev_best_node.id}, score: {dev_best_node.raw_reward}\n"
+ text += f"Grader score: {additional_scores['grader']}\n"
+ print(text)
+ results = [
+ {
+ "best_node": best_node.id,
+ "best_node_score": best_node.raw_reward,
+ "dev_best_node": dev_best_node.id,
+ "dev_best_node_score": dev_best_node.raw_reward,
+ "num_generated_codes": num_generated_codes,
+ "user_requirement": best_node.state["requirement"],
+ "tree_text": text,
+ "args": vars(self.args),
+ "scores": score_dict,
+ "additional_scores": additional_scores,
+ }
+ ]
+ self.save_result(results)
+ self.copy_notebook(best_node, "best")
+ self.copy_notebook(dev_best_node, "dev_best")
+ self.save_tree(text)
+
+ def copy_notebook(self, node, name):
+ node_dir = node.get_node_dir()
+ node_nb_dir = f"{node_dir}/Node-{node.id}.ipynb"
+ save_name = self.get_save_name()
+ copy_nb_dir = f"{self.result_path}/{save_name}_{name}.ipynb"
+ shutil.copy(node_nb_dir, copy_nb_dir)
+
+ def save_tree(self, tree_text):
+ save_name = self.get_save_name()
+ fpath = f"{self.result_path}/{save_name}_tree.txt"
+ with open(fpath, "w") as f:
+ f.write(tree_text)
diff --git a/metagpt/ext/sela/runner/mle_bench/instructions.py b/metagpt/ext/sela/runner/mle_bench/instructions.py
new file mode 100644
index 000000000..136726bcf
--- /dev/null
+++ b/metagpt/ext/sela/runner/mle_bench/instructions.py
@@ -0,0 +1,48 @@
+SUBMISSION_INSTURCTION = """
+- **Submission checker**: We provide a tool for checking the validity of your submission file, in the form of a server endpoint at `http://localhost:5000/validate`. If you post a submission file to the server (e.g. `curl -X POST -F "file=@${SUBMISSION_FILE}" ${SERVER_URL}`), you will receive a response message telling you if your submission is valid or not (note that this does NOT give you a score). For your convenience, we provide a script `/home/validate_submission.sh` which can help you make this submission to the validation server.
+"""
+INSTRUCTIONS = """
+You are participating in *MLE-bench*, an offline version of Kaggle competitions adapted to test the coding ability of agents like yourself.
+
+You will be given a machine learning task. You must solve the task by training a model and running the model on the test set to produce a submission file. You may not hand-label the data to produce a submission file.
+
+BENCHMARK INSTRUCTIONS
+------
+First, please read the instructions below which are specific to our benchmark setup. Note, these instructions can be found again in `runner/mle_bench/instructions.py`.
+
+- **Competition-specific instructions**: The instructions for the competition are available in `{dataset_dir}/description.md`. Read this file to understand the problem setup for the competition you are currently solving.
+- **Dataset**: The dataset files for the competition are available in `{dataset_dir}`.
+- **Submission**: We have simplified all competition types to use a simple CSV submission format. Regardless of what the competition instructions say, you MUST produce a submission file at `{output_dir}/submission.csv`. We will only evaluate submissions found at this path; failure to submit a `submission.csv` at this path will result in a failed attempt.
+- **Test set**: In general, since we do not have access to the original test labels, we have constructed our own train/test split for each competition. As such, the dataset you see in `{dataset_dir}` may differ slightly from what is described in the competition instructions. Nevertheless the files available to you should otherwise be similar to the original and you should proceed with the competition task on the train/test splits available to you.
+- **Cheating**: You are not allowed to view others' solutions to this competition. If we detect that your submission is plagiarized from others, the submission will be disqualified.
+
+Your submission will be graded based on the metric in the competition-specific instructions. Your goal is to achieve the best score possible on the competition task.
+
+Wherever there is a conflict between the points above and the competition-specific instructions in `{dataset_dir}/description.md`, the above points should always take priority since these are always true for our offline benchmark setup.
+"""
+
+INSTRUCTIONS_OBFUSCATED = """
+You are participating in *MLE-bench*, a benchmark designed to test the coding ability of agents like yourself.
+
+You will be given a machine learning task. You must solve the task by training a model and running the model on the test set to produce a submission file. You may not hand-label the data to produce a submission file.
+
+BENCHMARK INSTRUCTIONS
+------
+First, please read the instructions below which are specific to our benchmark setup. Note, these instructions can be found again in `runner/mle_bench/instructions.py`.
+
+- **Task-specific instructions**: The instructions for the task are available in `{dataset_dir}/description.md`. Read this file to understand the problem setup for the task you are currently solving.
+- **Dataset**: The dataset files for the task are available in `{dataset_dir}/`.
+- **Submission**: Regardless of what the task instructions say, you MUST produce a submission file at `{output_dir}/submission.csv`. We will only evaluate submissions found at this path; failure to submit a `submission.csv` at this path will result in a failed attempt.
+- **Test set**: In general, since we do not have access to the original test labels, we have constructed our own train/test split for each task. As such, the dataset you see in `{dataset_dir}` may differ slightly from what is described in the task instructions. Nevertheless the files available to you should otherwise be similar to the original and you should proceed with the task on the train/test splits available to you.
+- **Cheating**: You are not allowed to view others' solutions to this task. If we detect that your submission is plagiarized from others, the submission will be disqualified.
+
+Your submission will be graded based on the metric in the task-specific instructions. Your goal is to achieve the best score possible on the task.
+
+Wherever there is a conflict between the points above and the task-specific instructions in `{dataset_dir}/description.md`, the above points should always take priority since these are always true for our offline benchmark setup.
+"""
+
+ADDITIONAL_NOTES = """
+ADDITIONAL NOTES
+------
+- **Compute**: You have access to gpu with the appropriate drivers installed.
+"""
diff --git a/metagpt/ext/sela/runner/random_search.py b/metagpt/ext/sela/runner/random_search.py
new file mode 100644
index 000000000..b1f43ac0c
--- /dev/null
+++ b/metagpt/ext/sela/runner/random_search.py
@@ -0,0 +1,53 @@
+from metagpt.ext.sela.experimenter import Experimenter
+from metagpt.ext.sela.insights.instruction_generator import InstructionGenerator
+from metagpt.ext.sela.runner.runner import Runner
+from metagpt.ext.sela.utils import get_exp_pool_path
+
+EXPS_PROMPT = """
+When doing the tasks, you can refer to the insights below:
+{experience}
+
+"""
+
+
+class RandomSearchRunner(Runner):
+ result_path: str = "results/random_search"
+
+ async def run_experiment(self):
+ # state = create_initial_state(self.args.task, start_task_id=1, data_config=self.data_config, low_is_better=self.args.low_is_better, name="")
+ user_requirement = self.state["requirement"]
+ exp_pool_path = get_exp_pool_path(self.args.task, self.data_config, pool_name="ds_analysis_pool")
+ exp_pool = InstructionGenerator.load_insight_pool(
+ exp_pool_path, use_fixed_insights=self.args.use_fixed_insights
+ )
+ if self.args.rs_mode == "single":
+ exps = InstructionGenerator._random_sample(exp_pool, self.args.num_experiments)
+ exps = [exp["Analysis"] for exp in exps]
+ elif self.args.rs_mode == "set":
+ exps = []
+ for i in range(self.args.num_experiments):
+ exp_set = InstructionGenerator.sample_instruction_set(exp_pool)
+ exp_set_text = "\n".join([f"{exp['task_id']}: {exp['Analysis']}" for exp in exp_set])
+ exps.append(exp_set_text)
+ else:
+ raise ValueError(f"Invalid mode: {self.args.rs_mode}")
+
+ results = []
+ for i in range(self.args.num_experiments):
+ di = Experimenter(node_id=str(i), use_reflection=self.args.reflection, role_timeout=self.args.role_timeout)
+ di.role_dir = f"{di.role_dir}_{self.args.task}"
+ requirement = user_requirement + EXPS_PROMPT.format(experience=exps[i])
+ print(requirement)
+ score_dict = await self.run_di(di, requirement, run_idx=i)
+ results.append(
+ {
+ "idx": i,
+ "score_dict": score_dict,
+ "rs_mode": self.args.rs_mode,
+ "insights": exps[i],
+ "user_requirement": requirement,
+ "args": vars(self.args),
+ }
+ )
+ results = self.summarize_results(results)
+ self.save_result(results)
diff --git a/metagpt/ext/sela/runner/runner.py b/metagpt/ext/sela/runner/runner.py
new file mode 100644
index 000000000..4b5504e09
--- /dev/null
+++ b/metagpt/ext/sela/runner/runner.py
@@ -0,0 +1,133 @@
+import datetime
+import json
+import os
+
+import numpy as np
+import pandas as pd
+
+from metagpt.ext.sela.evaluation.evaluation import evaluate_score
+from metagpt.ext.sela.experimenter import Experimenter
+from metagpt.ext.sela.search.tree_search import create_initial_state
+from metagpt.ext.sela.utils import DATA_CONFIG, save_notebook
+
+
+class Runner:
+ result_path: str = "results/base"
+ data_config = DATA_CONFIG
+ start_task_id = 1
+
+ def __init__(self, args, **kwargs):
+ self.args = args
+ self.start_time_raw = datetime.datetime.now()
+ self.start_time = self.start_time_raw.strftime("%Y%m%d%H%M")
+ self.state = create_initial_state(
+ self.args.task,
+ start_task_id=self.start_task_id,
+ data_config=self.data_config,
+ args=self.args,
+ )
+
+ async def run_di(self, di, user_requirement, run_idx):
+ max_retries = 3
+ num_runs = 1
+ run_finished = False
+ while num_runs <= max_retries and not run_finished:
+ try:
+ await di.run(user_requirement)
+ score_dict = await di.get_score()
+ score_dict = self.evaluate(score_dict, self.state)
+ run_finished = True
+ except Exception as e:
+ print(f"Error: {e}")
+ num_runs += 1
+ # save_notebook(role=di, save_dir=self.result_path, name=f"{self.args.task}_{self.start_time}_{run_idx}")
+ save_name = self.get_save_name()
+ save_notebook(role=di, save_dir=self.result_path, name=f"{save_name}_{run_idx}")
+
+ if not run_finished:
+ score_dict = {"train_score": -1, "dev_score": -1, "test_score": -1, "score": -1}
+ return score_dict
+
+ def summarize_results(self, results):
+ dev_scores = [result["score_dict"]["dev_score"] for result in results]
+ best_dev_score = (
+ max(dev_scores)
+ if not self.args.low_is_better
+ else min([score for score in dev_scores if score != -1] + [np.inf])
+ )
+ best_score_idx = dev_scores.index(best_dev_score)
+
+ test_scores = [result["score_dict"]["test_score"] for result in results]
+ avg_score = sum(test_scores) / len(test_scores)
+ global_best_score = (
+ max(test_scores)
+ if not self.args.low_is_better
+ else min([score for i, score in enumerate(test_scores) if dev_scores[i] != -1] + [np.inf])
+ )
+
+ results.insert(
+ 0,
+ {
+ "best_dev_score": best_dev_score,
+ "best_dev_score_idx": best_score_idx,
+ "best_dev_test_score": test_scores[best_score_idx],
+ "avg_test_score": avg_score,
+ "global_best_test_score": global_best_score,
+ },
+ )
+ return results
+
+ async def run_experiment(self):
+ state = self.state
+ user_requirement = state["requirement"]
+ results = []
+
+ for i in range(self.args.num_experiments):
+ di = Experimenter(node_id="0", use_reflection=self.args.reflection, role_timeout=self.args.role_timeout)
+ score_dict = await self.run_di(di, user_requirement, run_idx=i)
+ results.append(
+ {"idx": i, "score_dict": score_dict, "user_requirement": user_requirement, "args": vars(self.args)}
+ )
+ self.save_result(results) # save intermediate results
+ results = self.summarize_results(results)
+
+ self.save_result(results)
+
+ def evaluate_prediction(self, split, state):
+ pred_path = os.path.join(state["work_dir"], state["task"], f"{split}_predictions.csv")
+ os.makedirs(state["node_dir"], exist_ok=True)
+ pred_node_path = os.path.join(state["node_dir"], f"{self.start_time}-{split}_predictions.csv")
+ gt_path = os.path.join(state["datasets_dir"][f"{split}_target"])
+ preds = pd.read_csv(pred_path)
+ preds = preds[preds.columns.tolist()[-1]]
+ preds.to_csv(pred_node_path, index=False)
+ gt = pd.read_csv(gt_path)["target"]
+ metric = state["dataset_config"]["metric"]
+ os.remove(pred_path)
+ return evaluate_score(preds, gt, metric)
+
+ def evaluate(self, score_dict, state):
+ scores = {
+ "dev_score": self.evaluate_prediction("dev", state),
+ "test_score": self.evaluate_prediction("test", state),
+ }
+ score_dict.update(scores)
+ return score_dict
+
+ def get_save_name(self):
+ return f"{self.args.exp_mode}-{self.args.task}_{self.start_time}"
+
+ def save_result(self, result):
+ end_time_raw = datetime.datetime.now()
+ end_time = end_time_raw.strftime("%Y%m%d%H%M")
+ time_info = {
+ "start_time": self.start_time,
+ "end_time": end_time,
+ "duration (seconds)": (end_time_raw - self.start_time_raw).seconds,
+ }
+ result = result.copy()
+ result.insert(0, time_info)
+ save_name = self.get_save_name()
+ os.makedirs(self.result_path, exist_ok=True)
+ with open(f"{self.result_path}/{save_name}.json", "w") as f:
+ json.dump(result, f, indent=4)
diff --git a/metagpt/ext/sela/scripts/run_cls.sh b/metagpt/ext/sela/scripts/run_cls.sh
new file mode 100644
index 000000000..f0ee5ddcf
--- /dev/null
+++ b/metagpt/ext/sela/scripts/run_cls.sh
@@ -0,0 +1,15 @@
+#!/bin/bash
+
+tasks=("smoker-status" "software-defects" "jasmine" "credit-g" "Click_prediction_small" "kick" "kc1" "titanic" "icr" "wine-quality-white" "mfeat-factors" "segment" "GesturePhaseSegmentationProcessed")
+
+
+for i in {1..3}
+do
+ for task in "${tasks[@]}"; do
+ echo "Running experiment for task: $task"
+ python run_experiment.py --exp_mode mcts --task "$task" --rollouts 10 --special_instruction stacking
+ echo "Experiment for task $task completed."
+ done
+done
+
+echo "All experiments completed."
diff --git a/metagpt/ext/sela/scripts/run_cls_mod.sh b/metagpt/ext/sela/scripts/run_cls_mod.sh
new file mode 100644
index 000000000..ae3622b7a
--- /dev/null
+++ b/metagpt/ext/sela/scripts/run_cls_mod.sh
@@ -0,0 +1,13 @@
+#!/bin/bash
+
+tasks=("banking77" "gnad10" "sms_spam" "oxford-iiit-pet" "stanford_cars" "fashion_mnist" )
+
+for i in {1..3}
+do
+ for task in "${tasks[@]}"; do
+ echo "Running experiment for task: $task"
+ python run_experiment.py --exp_mode mcts --task "$task" --rollouts 10
+ echo "Experiment for task $task completed."
+ done
+done
+echo "All experiments completed."
diff --git a/metagpt/ext/sela/scripts/run_reg.sh b/metagpt/ext/sela/scripts/run_reg.sh
new file mode 100644
index 000000000..f8a742886
--- /dev/null
+++ b/metagpt/ext/sela/scripts/run_reg.sh
@@ -0,0 +1,14 @@
+#!/bin/bash
+
+tasks=("concrete-strength" "Moneyball" "colleges" "SAT11-HAND-runtime-regression" "diamonds" "boston" "house-prices")
+
+for i in {1..3}
+do
+ for task in "${tasks[@]}"; do
+ echo "Running experiment for task: $task"
+ python run_experiment.py --exp_mode mcts --task "$task" --rollouts 10 --low_is_better --special_instruction stacking
+ echo "Experiment for task $task completed."
+ done
+done
+
+echo "All experiments completed."
diff --git a/metagpt/ext/sela/scripts/visualize_experiment.py b/metagpt/ext/sela/scripts/visualize_experiment.py
new file mode 100644
index 000000000..a6d980d11
--- /dev/null
+++ b/metagpt/ext/sela/scripts/visualize_experiment.py
@@ -0,0 +1,28 @@
+import networkx as nx
+
+from metagpt.ext.sela.evaluation.visualize_mcts import (
+ build_tree_recursive,
+ visualize_tree,
+)
+from metagpt.ext.sela.MCTS import MCTS, create_initial_state, initialize_di_root_node
+from metagpt.ext.sela.run_experiment import get_args
+from metagpt.ext.sela.utils import DATA_CONFIG
+
+if __name__ == "__main__":
+ args = get_args()
+ data_config = DATA_CONFIG
+ state = create_initial_state(args.task, 0, data_config, args=args)
+ role, node = initialize_di_root_node(state)
+ mcts = MCTS(
+ root_node=node,
+ max_depth=5,
+ use_fixed_insights=False,
+ )
+
+ mcts.load_tree()
+ mcts.load_node_order()
+ root = mcts.root_node
+ node_order = mcts.node_order
+ G = nx.DiGraph()
+ build_tree_recursive(G, "0", root, node_order)
+ visualize_tree(G, save_path=f"results/{args.task}-tree.png")
diff --git a/metagpt/ext/sela/search/search_algorithm.py b/metagpt/ext/sela/search/search_algorithm.py
new file mode 100644
index 000000000..ca47d8cf6
--- /dev/null
+++ b/metagpt/ext/sela/search/search_algorithm.py
@@ -0,0 +1,32 @@
+import numpy as np
+
+from metagpt.ext.sela.search.tree_search import BaseTreeSearch, Node
+
+
+class Greedy(BaseTreeSearch):
+ def best_child(self):
+ if len(self.children) == 0:
+ return self.root_node
+ all_children = [child for children in self.children.values() for child in children]
+ return max(all_children, key=lambda x: x.normalized_reward.get("dev_score", 0))
+
+
+class Random(BaseTreeSearch):
+ def best_child(self):
+ if len(self.children) == 0:
+ return self.root_node
+ all_children = [child for children in self.children.values() for child in children]
+ return np.random.choice(all_children)
+
+
+class MCTS(BaseTreeSearch):
+ def best_child(self):
+ def uct(node: Node):
+ n_visits = node.visited if node.visited else self.c_unvisited
+ avg_value = node.avg_value() if node.visited else node.value / self.c_unvisited
+ return avg_value + self.c_explore * np.sqrt(np.log(node.parent.visited) / n_visits)
+
+ if len(self.children) == 0:
+ return self.root_node
+ all_children = [child for children in self.children.values() for child in children]
+ return max(all_children, key=uct)
diff --git a/metagpt/ext/sela/search/tree_search.py b/metagpt/ext/sela/search/tree_search.py
new file mode 100644
index 000000000..eac26c86c
--- /dev/null
+++ b/metagpt/ext/sela/search/tree_search.py
@@ -0,0 +1,492 @@
+import json
+import os
+import pickle
+import shutil
+
+import numpy as np
+import pandas as pd
+
+from metagpt.ext.sela.data.custom_task import (
+ get_mle_bench_requirements,
+ get_mle_task_id,
+)
+from metagpt.ext.sela.data.dataset import (
+ generate_task_requirement,
+ get_split_dataset_path,
+)
+from metagpt.ext.sela.evaluation.evaluation import evaluate_score
+from metagpt.ext.sela.experimenter import Experimenter, TimeoutException
+from metagpt.ext.sela.insights.instruction_generator import InstructionGenerator
+from metagpt.ext.sela.utils import get_exp_pool_path, load_execute_notebook, mcts_logger
+from metagpt.tools.tool_recommend import ToolRecommender
+from metagpt.utils.common import read_json_file
+
+
+def initialize_di_root_node(state: dict, reflection: bool = True):
+ """
+ Initialize the root node of the decision tree.
+
+ Args:
+ state (dict): The initial state of the tree, containing:
+ - task (str): The task to be performed (e.g., "titanic").
+ - work_dir (str): The working directory.
+ - node_dir (str): The directory for the node.
+ - dataset_config (dict): The configuration of the dataset.
+ - datasets_dir (str): The directory of the datasets.
+ - exp_pool_path (str): The path to the experiment pool.
+ - requirement (str): The requirement for the task.
+ - has_run (bool): Whether the task has run.
+ - start_task_id (int): The ID of the starting task.
+ - low_is_better (bool): Whether a lower score is better.
+ - role_timeout (int): The timeout for the role.
+ - external_eval (bool): Whether to use external evaluation.
+ - custom_dataset_dir (str): The directory of the custom dataset.
+ reflection (bool, optional): Whether to use reflection. Defaults to True.
+
+ Returns:
+ tuple: A tuple containing the Experimenter role and the root Node.
+ """
+ role = Experimenter(
+ node_id="0",
+ start_task_id=state["start_task_id"],
+ use_reflection=reflection,
+ role_dir=state["node_dir"],
+ role_timeout=state["role_timeout"],
+ )
+ return role, Node(parent=None, state=state, action=None, value=0)
+
+
+def create_initial_state(task: str, start_task_id: int, data_config: dict, args):
+ """
+ Create the initial state of the tree.
+
+ Args:
+ task (str): The task to be performed.
+ start_task_id (int): The ID of the starting task.
+ data_config (dict): The configuration of the data.
+ Expected keys: 'datasets', 'work_dir', 'role_dir'.
+ args (Namespace): The arguments passed to the program.
+ Expected attributes: 'external_eval', 'custom_dataset_dir', 'special_instruction', 'name', 'low_is_better', 'role_timeout'.
+
+ Returns:
+ dict: The initial state of the tree.
+ """
+ external_eval = args.external_eval
+
+ if args.custom_dataset_dir:
+ dataset_config = None
+ datasets_dir = args.custom_dataset_dir
+ requirement = get_mle_bench_requirements(
+ args.custom_dataset_dir, data_config, special_instruction=args.special_instruction
+ )
+ exp_pool_path = None
+ # external_eval = False # make sure external eval is false if custom dataset is used
+ task = get_mle_task_id(args.custom_dataset_dir)
+ else:
+ dataset_config = data_config["datasets"][task]
+ if dataset_config["metric"] == "rmse":
+ args.low_is_better = True
+ datasets_dir = get_split_dataset_path(task, data_config)
+ requirement = generate_task_requirement(
+ task, data_config, is_di=True, special_instruction=args.special_instruction
+ )
+ exp_pool_path = get_exp_pool_path(task, data_config, pool_name="ds_analysis_pool")
+
+ initial_state = {
+ "task": task,
+ "work_dir": data_config["work_dir"],
+ "node_dir": os.path.join(data_config["work_dir"], data_config["role_dir"], f"{task}{args.name}"),
+ "dataset_config": dataset_config,
+ "datasets_dir": datasets_dir, # won't be used if external eval is used
+ "exp_pool_path": exp_pool_path,
+ "requirement": requirement,
+ "has_run": False,
+ "start_task_id": start_task_id,
+ "low_is_better": args.low_is_better,
+ "role_timeout": args.role_timeout,
+ "external_eval": external_eval,
+ "custom_dataset_dir": args.custom_dataset_dir,
+ }
+ os.makedirs(initial_state["node_dir"], exist_ok=True)
+ return initial_state
+
+
+class Node:
+ state: dict = {}
+ action: str = None
+ value: float = 0
+ visited: int = 0
+ children: list = []
+ normalized_reward: dict = {"train_score": 0, "dev_score": 0, "test_score": 0}
+ parent = None
+
+ def __init__(
+ self, parent=None, state: dict = None, action: str = None, value: float = 0, max_depth: int = 4, **kwargs
+ ):
+ self.state = state
+ self.action = action
+ self.value = value
+ self.raw_value = 0
+ self.raw_reward = dict()
+ self.parent = parent
+ self.children = []
+ self.max_depth = max_depth
+ self.depth = self.generate_depth()
+ self.id = self.generate_id()
+ if self.parent is not None:
+ self.save_node()
+
+ def avg_value(self):
+ if self.visited == 0:
+ return 0
+ return self.value / self.visited
+
+ def __hash__(self):
+ return hash(self.id)
+
+ def save_node(self):
+ os.makedirs(self.state["node_dir"], exist_ok=True)
+ with open(os.path.join(self.state["node_dir"], f"Node-{self.id}.pkl"), "wb") as f:
+ pickle.dump(self, f)
+
+ def load_node(self):
+ with open(os.path.join(self.state["node_dir"], f"Node-{self.id}.pkl"), "rb") as f:
+ return pickle.load(f)
+
+ def get_depth(self):
+ return self.depth
+
+ def get_node_dir(self):
+ return self.state["node_dir"]
+
+ def generate_depth(self):
+ if self.parent is None:
+ return 0
+ else:
+ return self.parent.depth + 1
+
+ def generate_id(self):
+ if self.parent is None:
+ return "0"
+ else:
+ num_sibling = len(self.parent.children)
+ return f"{self.parent.id}-{num_sibling}"
+
+ def is_terminal(self):
+ return int(self.state["start_task_id"]) == self.max_depth + 1 # TODO: Check if this is correct or +1
+
+ def is_fully_expanded(self):
+ return len(self.children) > 0
+
+ def add_child(self, child_node):
+ self.children.append(child_node)
+
+ def update(self, reward: dict, child_node=None):
+ if child_node is not None:
+ child_role = child_node.load_role()
+ role = self.load_role()
+ role.update_til_start_task(child_role)
+ role.save_state()
+ else:
+ self.raw_value = reward["test_score"]
+ self.value += reward["score"]
+ self.visited += 1
+ self.save_node()
+
+ def get_role_path(self):
+ fname = f"Node-{self.id}.json"
+ role_path = os.path.join(self.state["node_dir"], fname)
+ return role_path
+
+ def load_role(self):
+ role_dict = read_json_file(self.get_role_path())
+ if role_dict.get("tool_recommender") is None:
+ role_dict["tool_recommender"] = ToolRecommender()
+ elif isinstance(role_dict.get("tool_recommender", {}).get("tools"), dict):
+ role_dict["tool_recommender"]["tools"] = list(role_dict["tool_recommender"]["tools"].keys())
+ role = Experimenter(**role_dict)
+ if self.parent is not None: # TODO: Check this
+ parent_role = self.parent.load_role()
+ role.update_til_start_task(parent_role, backward=False)
+ role.remap_tasks()
+ return role
+
+ def save_new_role(self, role: Experimenter):
+ role.node_id = self.id
+ role.start_task_id = self.state["start_task_id"]
+ role.state_saved = False
+ role.change_next_instruction(self.action)
+ mcts_logger.log("MCTS", f"Saving new role: {role.node_id}")
+ role = role.model_copy()
+ role.save_state(static_save=True)
+
+ async def expand(self, max_children: int, instruction_generator: InstructionGenerator):
+ if self.is_fully_expanded():
+ return
+ role = self.load_role()
+ original_instruction = role.get_next_instruction()
+ insights = await instruction_generator.generate_new_instructions(
+ task_id=role.start_task_id + 1,
+ original_instruction=original_instruction,
+ max_num=max_children,
+ )
+ new_state = self.state.copy()
+ new_state["start_task_id"] += 1
+ for insight in insights:
+ new_role = role.model_copy()
+ node = Node(parent=self, state=new_state, action=insight, value=0)
+ node.save_new_role(new_role)
+ self.add_child(node)
+
+ def get_predictions_path(self, split):
+ return os.path.join(self.state["node_dir"], f"Node-{self.id}-{split}_predictions.csv")
+
+ def get_and_move_predictions(self, split):
+ if not os.path.exists(self.get_predictions_path(split)):
+ pred_path = os.path.join(self.state["work_dir"], self.state["task"], f"{split}_predictions.csv")
+ shutil.copy(pred_path, self.get_predictions_path(split))
+ os.remove(pred_path)
+ return pd.read_csv(self.get_predictions_path(split))
+
+ def get_gt(self, split):
+ gt_path = os.path.join(self.state["datasets_dir"][f"{split}_target"])
+ return pd.read_csv(gt_path)
+
+ def evaluate_prediction(self, split):
+ preds = self.get_and_move_predictions(split)["target"]
+ gt = self.get_gt(split)["target"]
+ metric = self.state["dataset_config"]["metric"]
+ return evaluate_score(preds, gt, metric)
+
+ def evaluate_simulation(self, score_dict):
+ if self.state["external_eval"]: # use external evaluation
+ scores = {"dev_score": self.evaluate_prediction("dev"), "test_score": self.evaluate_prediction("test")}
+ scores["score"] = scores["dev_score"]
+ score_dict.update(scores)
+ else:
+ self.get_and_move_predictions("dev")
+ self.get_and_move_predictions("test")
+ return score_dict
+
+ async def run_node(self, role: Experimenter = None):
+ if self.is_terminal() and role is not None:
+ if role.state_saved:
+ return self.raw_reward
+
+ max_retries = 3
+ num_runs = 1
+ run_finished = False
+ while num_runs <= max_retries and not run_finished:
+ try:
+ if not role:
+ role = self.load_role()
+ await load_execute_notebook(role) # execute previous notebook's code
+ await role.run(with_message="continue")
+ else:
+ await role.run(with_message=self.state["requirement"])
+ score_dict = await role.get_score()
+ score_dict = self.evaluate_simulation(score_dict)
+ self.raw_reward = score_dict
+ run_finished = True
+ except TimeoutException as e:
+ mcts_logger.log("MCTS", f"Role-level timeout: {e}")
+ break
+ except Exception as e:
+ mcts_logger.log("MCTS", f"Error in running the role: {e}")
+ num_runs += 1
+
+ if not run_finished:
+ mcts_logger.log("MCTS", f"Role {role.node_id} failed to run")
+ if self.state["low_is_better"]:
+ score_dict = {"test_score": np.inf, "dev_score": np.inf, "score": np.inf}
+ else:
+ score_dict = {"test_score": 0, "dev_score": 0, "score": 0}
+ self.raw_reward = score_dict
+ if self.state["low_is_better"]:
+ # normalized the score to be between 0 and 1, and higher is better
+ def normalize_score(score):
+ if score == -1:
+ return 0
+ return 1 / (1 + score)
+
+ score_dict = {k: normalize_score(v) for k, v in score_dict.items()}
+ self.normalized_reward = score_dict
+ result_dict = role.get_solution()
+ return score_dict, result_dict
+
+
+class BaseTreeSearch:
+ # data_path
+ root_node: Node = None
+ children: dict = {}
+ max_depth: int = None
+ c_explore: float = 1.4
+ c_unvisited: float = 0.8
+ node_order: list = []
+ # insight generator
+ instruction_generator: InstructionGenerator = None
+
+ def __init__(self, root_node: Node, max_depth: int, use_fixed_insights: bool):
+ self.root_node = root_node
+ self.max_depth = max_depth
+ self.use_fixed_insights = use_fixed_insights
+
+ def select(self, node: Node):
+ node = self.best_child()
+ mcts_logger.log("MCTS", f"Selected node id: {node.id}")
+ return node
+
+ def best_child(self):
+ raise NotImplementedError
+
+ async def expand(self, node: Node, max_children=5):
+ await node.expand(max_children, self.instruction_generator)
+ if node not in self.children or not self.children[node]:
+ self.children[node] = node.children
+ return node.children
+
+ async def simulate(self, node: Node, role=None):
+ "Returns the reward for a random simulation (to completion) of `node`"
+ mcts_logger.log("MCTS", f"Start simulating node {node.id}:")
+ while node.children:
+ node = np.random.choice(node.children)
+ reward, result_dict = await node.run_node(role)
+ mcts_logger.log("MCTS", f"Simulated node's reward: {reward}")
+ # TODO: add new insights
+ return reward
+
+ def backpropagate(self, node: Node, reward: dict):
+ child_node = node
+ node.update(reward)
+ node = node.parent
+ while node is not None:
+ node.update(reward, child_node)
+ node, child_node = node.parent, node
+
+ def best_path(self, root: Node):
+ best_child = root
+ global_best_score = root.normalized_reward["test_score"]
+ dev_best_score = root.normalized_reward["dev_score"]
+
+ def bfs(node: Node, best_score: float, best_child: Node, split: str):
+ assert split in ["test_score", "dev_score"]
+ if node not in self.children:
+ return best_score, best_child
+ for child in self.children[node]:
+ score = child.normalized_reward[split]
+ print(child.id, split, score)
+ if score > best_score:
+ best_score = score
+ best_child = child
+ best_score, best_child = bfs(child, best_score, best_child, split)
+ return best_score, best_child
+
+ _, global_best_child = bfs(root, global_best_score, best_child, "test_score")
+ _, dev_best_child = bfs(root, dev_best_score, best_child, "dev_score")
+
+ return {"dev_best": dev_best_child, "global_best": global_best_child, "scores": self.get_score_order_dict()}
+
+ def get_num_simulations(self):
+ return self.root_node.visited
+
+ def save_node_order(self, node_id: str):
+ self.node_order.append(node_id)
+ with open(os.path.join(self.root_node.state["node_dir"], "node_order.json"), "w") as f:
+ json.dump(self.node_order, f)
+
+ def load_node_order(self):
+ with open(os.path.join(self.root_node.state["node_dir"], "node_order.json"), "r") as f:
+ self.node_order = json.load(f)
+
+ def get_score_order_dict(self):
+ scores = {"dev": [], "test": [], "dev_raw": [], "test_raw": []}
+ for node_id in self.node_order:
+ node = Node(parent=None, state=self.root_node.state, action=None, value=0)
+ node.id = node_id
+ node = node.load_node()
+ scores["dev"].append(node.normalized_reward["dev_score"])
+ scores["test"].append(node.normalized_reward["test_score"])
+ scores["dev_raw"].append(node.raw_reward["dev_score"])
+ scores["test_raw"].append(node.raw_reward["test_score"])
+ return scores
+
+ async def search(self, state: dict, args):
+ reflection = args.reflection
+ load_tree = args.load_tree
+ rollouts = args.rollouts
+ from_scratch = args.from_scratch
+ role, root = initialize_di_root_node(state, reflection=reflection)
+ self.root_node = root
+ self.instruction_generator = InstructionGenerator(
+ state=state, use_fixed_insights=self.use_fixed_insights, from_scratch=from_scratch
+ )
+ await self.instruction_generator.initialize()
+
+ tree_loaded = False
+ if load_tree:
+ tree_loaded = self.load_tree()
+ mcts_logger.log("MCTS", f"Number of simulations: {self.get_num_simulations()}")
+ mcts_logger.log("MCTS", f"Tree loaded: {tree_loaded}")
+
+ if not tree_loaded:
+ rollouts -= 2 # 2 rollouts for the initial tree
+ if rollouts < 0:
+ raise ValueError("Rollouts must be greater than 2 if there is no tree to load")
+ self.children[root] = []
+ reward = await self.simulate(root, role)
+ self.backpropagate(root, reward)
+ node, reward = await self.expand_and_simulate(root)
+ # self.backpropagate(node, reward)
+ self.save_node_order(root.id)
+ self.save_node_order(node.id)
+ else:
+ root = self.root_node
+ self.load_node_order()
+
+ for _ in range(rollouts): # number of rollouts
+ mcts_logger.log("MCTS", f"Start the next rollout {_+1}")
+ node = self.select(root)
+ if node.is_terminal():
+ if node.raw_value == 0:
+ reward = await self.simulate(node)
+ else:
+ reward = {"test_score": node.raw_value, "score": node.raw_reward["score"]}
+ mcts_logger.log("MCTS", f"Terminal node's reward: {reward}")
+ self.backpropagate(node, reward)
+ else:
+ node, reward = await self.expand_and_simulate(node)
+ # self.backpropagate(node, reward)
+ self.save_node_order(node.id)
+ return self.best_path(root)
+
+ async def expand_and_simulate(self, node: Node):
+ # Expand and randomly select a child node, then simulate it
+ if node.visited > 0:
+ children = await self.expand(node)
+ node = np.random.choice(children)
+ reward = await self.simulate(node)
+ self.backpropagate(node, reward)
+ return node, reward
+
+ def load_tree(self):
+ def load_children_node(node: Node):
+ mcts_logger.log("MCTS", f"Load node {node.id}'s child: {node.children}")
+ if node.is_terminal() or not node.children:
+ return
+ for child in node.children:
+ child.load_node()
+ self.children[child] = child.children
+ load_children_node(child)
+
+ # Load all pkl files in the node_dir
+ all_pkl_files = os.listdir(self.root_node.state["node_dir"])
+ all_pkl_files = [f for f in all_pkl_files if f.endswith(".pkl")]
+ if os.path.exists(os.path.join(self.root_node.state["node_dir"], "Node-0.pkl")):
+ with open(os.path.join(self.root_node.state["node_dir"], "Node-0.pkl"), "rb") as f:
+ self.root_node = pickle.load(f)
+ self.children[self.root_node] = self.root_node.children
+ load_children_node(self.root_node)
+
+ if self.children:
+ return True
+ return False
diff --git a/metagpt/ext/sela/utils.py b/metagpt/ext/sela/utils.py
new file mode 100644
index 000000000..21b311e7f
--- /dev/null
+++ b/metagpt/ext/sela/utils.py
@@ -0,0 +1,130 @@
+import os
+import re
+from datetime import datetime
+from pathlib import Path
+
+import nbformat
+import yaml
+from loguru import logger as _logger
+from nbclient import NotebookClient
+from nbformat.notebooknode import NotebookNode
+
+from metagpt.roles.role import Role
+
+
+def load_data_config(file_path="data.yaml"):
+ with open(file_path, "r") as stream:
+ data_config = yaml.safe_load(stream)
+ return data_config
+
+
+DATASET_CONFIG = load_data_config("datasets.yaml")
+DATA_CONFIG = load_data_config()
+DATA_CONFIG["datasets"] = DATASET_CONFIG["datasets"]
+
+
+def get_mcts_logger():
+ logfile_level = "DEBUG"
+ name: str = None
+ current_date = datetime.now()
+ formatted_date = current_date.strftime("%Y%m%d")
+ log_name = f"{name}_{formatted_date}" if name else formatted_date # name a log with prefix name
+
+ # _logger.remove()
+ _logger.level("MCTS", color="", no=25)
+ # _logger.add(sys.stderr, level=print_level)
+ _logger.add(Path(DATA_CONFIG["work_dir"]) / DATA_CONFIG["role_dir"] / f"{log_name}.txt", level=logfile_level)
+ _logger.propagate = False
+ return _logger
+
+
+mcts_logger = get_mcts_logger()
+
+
+def get_exp_pool_path(task_name, data_config, pool_name="analysis_pool"):
+ datasets_dir = data_config["datasets_dir"]
+ if task_name in data_config["datasets"]:
+ dataset = data_config["datasets"][task_name]
+ data_path = os.path.join(datasets_dir, dataset["dataset"])
+ else:
+ raise ValueError(
+ f"Dataset {task_name} not found in config file. Available datasets: {data_config['datasets'].keys()}"
+ )
+ exp_pool_path = os.path.join(data_path, f"{pool_name}.json")
+ if not os.path.exists(exp_pool_path):
+ return None
+ return exp_pool_path
+
+
+def change_plan(role, plan):
+ print(f"Change next plan to: {plan}")
+ tasks = role.planner.plan.tasks
+ finished = True
+ for i, task in enumerate(tasks):
+ if not task.code:
+ finished = False
+ break
+ if not finished:
+ tasks[i].plan = plan
+ return finished
+
+
+def is_cell_to_delete(cell: NotebookNode) -> bool:
+ if "outputs" in cell:
+ for output in cell["outputs"]:
+ if output and "traceback" in output:
+ return True
+ return False
+
+
+def process_cells(nb: NotebookNode) -> NotebookNode:
+ new_cells = []
+ i = 1
+ for cell in nb["cells"]:
+ if cell["cell_type"] == "code" and not is_cell_to_delete(cell):
+ cell["execution_count"] = i
+ new_cells.append(cell)
+ i = i + 1
+ nb["cells"] = new_cells
+ return nb
+
+
+def save_notebook(role: Role, save_dir: str = "", name: str = "", save_to_depth=False):
+ save_dir = Path(save_dir)
+ tasks = role.planner.plan.tasks
+ nb = process_cells(role.execute_code.nb)
+ os.makedirs(save_dir, exist_ok=True)
+ file_path = save_dir / f"{name}.ipynb"
+ nbformat.write(nb, file_path)
+
+ if save_to_depth:
+ clean_file_path = save_dir / f"{name}_clean.ipynb"
+ codes = [task.code for task in tasks if task.code]
+ clean_nb = nbformat.v4.new_notebook()
+ for code in codes:
+ clean_nb.cells.append(nbformat.v4.new_code_cell(code))
+ nbformat.write(clean_nb, clean_file_path)
+
+
+async def load_execute_notebook(role):
+ tasks = role.planner.plan.tasks
+ codes = [task.code for task in tasks if task.code]
+ executor = role.execute_code
+ executor.nb = nbformat.v4.new_notebook()
+ executor.nb_client = NotebookClient(executor.nb, timeout=role.role_timeout)
+ # await executor.build()
+ for code in codes:
+ outputs, success = await executor.run(code)
+ print(f"Execution success: {success}, Output: {outputs}")
+ print("Finish executing the loaded notebook")
+ return executor
+
+
+def clean_json_from_rsp(text):
+ pattern = r"```json(.*?)```"
+ matches = re.findall(pattern, text, re.DOTALL)
+ if matches:
+ json_str = "\n".join(matches)
+ return json_str
+ else:
+ return ""
diff --git a/metagpt/ext/spo/__init__.py b/metagpt/ext/spo/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/metagpt/ext/spo/app.py b/metagpt/ext/spo/app.py
new file mode 100644
index 000000000..20895a420
--- /dev/null
+++ b/metagpt/ext/spo/app.py
@@ -0,0 +1,301 @@
+import asyncio
+from pathlib import Path
+from typing import Dict
+
+import streamlit as st
+import yaml
+from loguru import logger as _logger
+
+from metagpt.const import METAGPT_ROOT
+from metagpt.ext.spo.components.optimizer import PromptOptimizer
+from metagpt.ext.spo.utils.llm_client import SPO_LLM, RequestType
+
+
+def load_yaml_template(template_path: Path) -> Dict:
+ if template_path.exists():
+ with open(template_path, "r", encoding="utf-8") as f:
+ return yaml.safe_load(f)
+ return {"prompt": "", "requirements": "", "count": None, "qa": [{"question": "", "answer": ""}]}
+
+
+def save_yaml_template(template_path: Path, data: Dict) -> None:
+ template_format = {
+ "prompt": str(data.get("prompt", "")),
+ "requirements": str(data.get("requirements", "")),
+ "count": data.get("count"),
+ "qa": [
+ {"question": str(qa.get("question", "")).strip(), "answer": str(qa.get("answer", "")).strip()}
+ for qa in data.get("qa", [])
+ ],
+ }
+
+ template_path.parent.mkdir(parents=True, exist_ok=True)
+
+ with open(template_path, "w", encoding="utf-8") as f:
+ yaml.dump(template_format, f, allow_unicode=True, sort_keys=False, default_flow_style=False, indent=2)
+
+
+def display_optimization_results(result_data):
+ for result in result_data:
+ round_num = result["round"]
+ success = result["succeed"]
+ prompt = result["prompt"]
+
+ with st.expander(f"Round {round_num} {':white_check_mark:' if success else ':x:'}"):
+ st.markdown("**Prompt:**")
+ st.code(prompt, language="text")
+ st.markdown(" ", unsafe_allow_html=True)
+
+ col1, col2 = st.columns(2)
+ with col1:
+ st.markdown(f"**Status:** {'Success ✅ ' if success else 'Failed ❌ '}")
+ with col2:
+ st.markdown(f"**Tokens:** {result['tokens']}")
+
+ st.markdown("**Answers:**")
+ for idx, answer in enumerate(result["answers"]):
+ st.markdown(f"**Question {idx + 1}:**")
+ st.text(answer["question"])
+ st.markdown("**Answer:**")
+ st.text(answer["answer"])
+ st.markdown("---")
+
+ # Summary
+ success_count = sum(1 for r in result_data if r["succeed"])
+ total_rounds = len(result_data)
+
+ st.markdown("### Summary")
+ col1, col2 = st.columns(2)
+ with col1:
+ st.metric("Total Rounds", total_rounds)
+ with col2:
+ st.metric("Successful Rounds", success_count)
+
+
+def main():
+ if "optimization_results" not in st.session_state:
+ st.session_state.optimization_results = []
+
+ st.markdown(
+ """
+
+
+
SPO | Self-Supervised Prompt Optimization 🤖
+
+
+
+
+
+
+
+
+ A framework for self-supervised prompt optimization
+
+
+ """,
+ unsafe_allow_html=True,
+ )
+
+ # Sidebar for configurations
+ with st.sidebar:
+ st.header("Configuration")
+
+ # Template Selection/Creation
+ settings_path = Path("metagpt/ext/spo/settings")
+ existing_templates = [f.stem for f in settings_path.glob("*.yaml")]
+
+ template_mode = st.radio("Template Mode", ["Use Existing", "Create New"])
+
+ if template_mode == "Use Existing":
+ template_name = st.selectbox("Select Template", existing_templates)
+ else:
+ template_name = st.text_input("New Template Name")
+ if template_name and not template_name.endswith(".yaml"):
+ template_name = f"{template_name}"
+
+ # LLM Settings
+ st.subheader("LLM Settings")
+ opt_model = st.selectbox(
+ "Optimization Model", ["claude-3-5-sonnet-20240620", "gpt-4o", "gpt-4o-mini", "deepseek-chat"], index=0
+ )
+ opt_temp = st.slider("Optimization Temperature", 0.0, 1.0, 0.7)
+
+ eval_model = st.selectbox(
+ "Evaluation Model", ["gpt-4o-mini", "claude-3-5-sonnet-20240620", "gpt-4o", "deepseek-chat"], index=0
+ )
+ eval_temp = st.slider("Evaluation Temperature", 0.0, 1.0, 0.3)
+
+ exec_model = st.selectbox(
+ "Execution Model", ["gpt-4o-mini", "claude-3-5-sonnet-20240620", "gpt-4o", "deepseek-chat"], index=0
+ )
+ exec_temp = st.slider("Execution Temperature", 0.0, 1.0, 0.0)
+
+ # Optimizer Settings
+ st.subheader("Optimizer Settings")
+ initial_round = st.number_input("Initial Round", 1, 100, 1)
+ max_rounds = st.number_input("Maximum Rounds", 1, 100, 10)
+
+ # Main content area
+ st.header("Template Configuration")
+
+ if template_name:
+ template_path = settings_path / f"{template_name}.yaml"
+ template_data = load_yaml_template(template_path)
+
+ if "current_template" not in st.session_state or st.session_state.current_template != template_name:
+ st.session_state.current_template = template_name
+ st.session_state.qas = template_data.get("qa", [])
+
+ # Edit template sections
+ prompt = st.text_area("Prompt", template_data.get("prompt", ""), height=100)
+ requirements = st.text_area("Requirements", template_data.get("requirements", ""), height=100)
+
+ # qa section
+ st.subheader("Q&A Examples")
+
+ # Add new qa button
+ if st.button("Add New Q&A"):
+ st.session_state.qas.append({"question": "", "answer": ""})
+
+ # Edit qas
+ new_qas = []
+ for i in range(len(st.session_state.qas)):
+ st.markdown(f"**QA #{i + 1}**")
+ col1, col2, col3 = st.columns([45, 45, 10])
+
+ with col1:
+ question = st.text_area(
+ f"Question {i + 1}", st.session_state.qas[i].get("question", ""), key=f"q_{i}", height=100
+ )
+ with col2:
+ answer = st.text_area(
+ f"Answer {i + 1}", st.session_state.qas[i].get("answer", ""), key=f"a_{i}", height=100
+ )
+ with col3:
+ if st.button("🗑️", key=f"delete_{i}"):
+ st.session_state.qas.pop(i)
+ st.rerun()
+
+ new_qas.append({"question": question, "answer": answer})
+
+ # Save template button
+ if st.button("Save Template"):
+ new_template_data = {"prompt": prompt, "requirements": requirements, "count": None, "qa": new_qas}
+
+ save_yaml_template(template_path, new_template_data)
+
+ st.session_state.qas = new_qas
+ st.success(f"Template saved to {template_path}")
+
+ st.subheader("Current Template Preview")
+ preview_data = {"qa": new_qas, "requirements": requirements, "prompt": prompt}
+ st.code(yaml.dump(preview_data, allow_unicode=True), language="yaml")
+
+ st.subheader("Optimization Logs")
+ log_container = st.empty()
+
+ class StreamlitSink:
+ def write(self, message):
+ current_logs = st.session_state.get("logs", [])
+ current_logs.append(message.strip())
+ st.session_state.logs = current_logs
+
+ log_container.code("\n".join(current_logs), language="plaintext")
+
+ streamlit_sink = StreamlitSink()
+ _logger.remove()
+
+ def prompt_optimizer_filter(record):
+ return "optimizer" in record["name"].lower()
+
+ _logger.add(
+ streamlit_sink.write,
+ format="{time:YYYY-MM-DD HH:mm:ss.SSS} | {level: <8} | {name}:{function}:{line} - {message}",
+ filter=prompt_optimizer_filter,
+ )
+ _logger.add(METAGPT_ROOT / "logs/{time:YYYYMMDD}.txt", level="DEBUG")
+
+ # Start optimization button
+ if st.button("Start Optimization"):
+ try:
+ # Initialize LLM
+ SPO_LLM.initialize(
+ optimize_kwargs={"model": opt_model, "temperature": opt_temp},
+ evaluate_kwargs={"model": eval_model, "temperature": eval_temp},
+ execute_kwargs={"model": exec_model, "temperature": exec_temp},
+ )
+
+ # Create optimizer instance
+ optimizer = PromptOptimizer(
+ optimized_path="workspace",
+ initial_round=initial_round,
+ max_rounds=max_rounds,
+ template=f"{template_name}.yaml",
+ name=template_name,
+ )
+
+ # Run optimization with progress bar
+ with st.spinner("Optimizing prompts..."):
+ optimizer.optimize()
+
+ st.success("Optimization completed!")
+
+ st.header("Optimization Results")
+
+ prompt_path = optimizer.root_path / "prompts"
+ result_data = optimizer.data_utils.load_results(prompt_path)
+
+ st.session_state.optimization_results = result_data
+
+ except Exception as e:
+ st.error(f"An error occurred: {str(e)}")
+ _logger.error(f"Error during optimization: {str(e)}")
+
+ if st.session_state.optimization_results:
+ st.header("Optimization Results")
+ display_optimization_results(st.session_state.optimization_results)
+
+ st.markdown("---")
+ st.subheader("Test Optimized Prompt")
+ col1, col2 = st.columns(2)
+
+ with col1:
+ test_prompt = st.text_area("Optimized Prompt", value="", height=200, key="test_prompt")
+
+ with col2:
+ test_question = st.text_area("Your Question", value="", height=200, key="test_question")
+
+ if st.button("Test Prompt"):
+ if test_prompt and test_question:
+ try:
+ with st.spinner("Generating response..."):
+ SPO_LLM.initialize(
+ optimize_kwargs={"model": opt_model, "temperature": opt_temp},
+ evaluate_kwargs={"model": eval_model, "temperature": eval_temp},
+ execute_kwargs={"model": exec_model, "temperature": exec_temp},
+ )
+
+ llm = SPO_LLM.get_instance()
+ messages = [{"role": "user", "content": f"{test_prompt}\n\n{test_question}"}]
+
+ async def get_response():
+ return await llm.responser(request_type=RequestType.EXECUTE, messages=messages)
+
+ loop = asyncio.new_event_loop()
+ asyncio.set_event_loop(loop)
+ try:
+ response = loop.run_until_complete(get_response())
+ finally:
+ loop.close()
+
+ st.subheader("Response:")
+ st.markdown(response)
+
+ except Exception as e:
+ st.error(f"Error generating response: {str(e)}")
+ else:
+ st.warning("Please enter both prompt and question.")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/metagpt/ext/spo/components/__init__.py b/metagpt/ext/spo/components/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/metagpt/ext/spo/components/evaluator.py b/metagpt/ext/spo/components/evaluator.py
new file mode 100644
index 000000000..952ef211b
--- /dev/null
+++ b/metagpt/ext/spo/components/evaluator.py
@@ -0,0 +1,75 @@
+# -*- coding: utf-8 -*-
+# @Date : 8/23/2024 10:00 AM
+# @Author : all
+# @Desc : Evaluation for different datasets
+import asyncio
+import random
+from typing import Any, Dict
+
+from metagpt.ext.spo.prompts.evaluate_prompt import EVALUATE_PROMPT
+from metagpt.ext.spo.utils import load
+from metagpt.ext.spo.utils.llm_client import SPO_LLM, RequestType, extract_content
+from metagpt.logs import logger
+
+
+class QuickExecute:
+ """
+ Execute Prompt
+ """
+
+ def __init__(self, prompt: str):
+ self.prompt = prompt
+ self.llm = SPO_LLM.get_instance()
+
+ async def prompt_execute(self) -> tuple[Any]:
+ _, _, qa, _ = load.load_meta_data()
+ answers = []
+
+ async def fetch_answer(q: str) -> Dict[str, Any]:
+ messages = [{"role": "user", "content": f"{self.prompt}\n\n{q}"}]
+ try:
+ answer = await self.llm.responser(request_type=RequestType.EXECUTE, messages=messages)
+ return {"question": q, "answer": answer}
+ except Exception as e:
+ return {"question": q, "answer": str(e)}
+
+ tasks = [fetch_answer(item["question"]) for item in qa]
+ answers = await asyncio.gather(*tasks)
+
+ return answers
+
+
+class QuickEvaluate:
+ """
+ Complete the evaluation for different answers here.
+ """
+
+ def __init__(self):
+ self.llm = SPO_LLM.get_instance()
+
+ async def prompt_evaluate(self, samples: dict, new_samples: dict) -> bool:
+ _, requirement, qa, _ = load.load_meta_data()
+
+ if random.random() < 0.5:
+ samples, new_samples = new_samples, samples
+ is_swapped = True
+ else:
+ is_swapped = False
+
+ messages = [
+ {
+ "role": "user",
+ "content": EVALUATE_PROMPT.format(
+ requirement=requirement, sample=samples, new_sample=new_samples, answers=str(qa)
+ ),
+ }
+ ]
+
+ try:
+ response = await self.llm.responser(request_type=RequestType.EVALUATE, messages=messages)
+ choose = extract_content(response, "choose")
+ return choose == "A" if is_swapped else choose == "B"
+
+ except Exception as e:
+ logger.error(e)
+ return False
diff --git a/metagpt/ext/spo/components/optimizer.py b/metagpt/ext/spo/components/optimizer.py
new file mode 100644
index 000000000..0ce588f44
--- /dev/null
+++ b/metagpt/ext/spo/components/optimizer.py
@@ -0,0 +1,136 @@
+# -*- coding: utf-8 -*-
+# @Date : 8/12/2024 22:00 PM
+# @Author : issac
+# @Desc : optimizer for prompt
+
+import asyncio
+from pathlib import Path
+from typing import List
+
+from metagpt.ext.spo.prompts.optimize_prompt import PROMPT_OPTIMIZE_PROMPT
+from metagpt.ext.spo.utils import load
+from metagpt.ext.spo.utils.data_utils import DataUtils
+from metagpt.ext.spo.utils.evaluation_utils import EvaluationUtils
+from metagpt.ext.spo.utils.llm_client import SPO_LLM, RequestType, extract_content
+from metagpt.ext.spo.utils.prompt_utils import PromptUtils
+from metagpt.logs import logger
+
+
+class PromptOptimizer:
+ def __init__(
+ self,
+ optimized_path: str = None,
+ initial_round: int = 1,
+ max_rounds: int = 10,
+ name: str = "",
+ template: str = "",
+ ) -> None:
+ self.name = name
+ self.root_path = Path(optimized_path) / self.name
+ self.top_scores = []
+ self.round = initial_round
+ self.max_rounds = max_rounds
+ self.template = template
+
+ self.prompt_utils = PromptUtils(self.root_path)
+ self.data_utils = DataUtils(self.root_path)
+ self.evaluation_utils = EvaluationUtils(self.root_path)
+ self.llm = SPO_LLM.get_instance()
+
+ def optimize(self):
+ for opt_round in range(self.max_rounds):
+ loop = asyncio.new_event_loop()
+ asyncio.set_event_loop(loop)
+ loop.run_until_complete(self._optimize_prompt())
+ self.round += 1
+
+ self.show_final_result()
+
+ def show_final_result(self):
+ best_round = self.data_utils.get_best_round()
+
+ logger.info("\n" + "=" * 50)
+ logger.info("\n🏆 OPTIMIZATION COMPLETED - FINAL RESULTS 🏆\n")
+ logger.info(f"\n📌 Best Performing Round: {best_round['round']}")
+ logger.info(f"\n🎯 Final Optimized Prompt:\n{best_round['prompt']}")
+ logger.info("\n" + "=" * 50 + "\n")
+
+ async def _optimize_prompt(self):
+ prompt_path = self.root_path / "prompts"
+ load.set_file_name(self.template)
+ data = self.data_utils.load_results(prompt_path)
+
+ if self.round == 1:
+ await self._handle_first_round(prompt_path, data)
+ return
+
+ directory = self.prompt_utils.create_round_directory(prompt_path, self.round)
+ new_prompt = await self._generate_optimized_prompt()
+ self.prompt = new_prompt
+
+ logger.info(f"\nRound {self.round} Prompt: {self.prompt}\n")
+ self.prompt_utils.write_prompt(directory, prompt=self.prompt)
+
+ success, answers = await self._evaluate_new_prompt(prompt_path, data, directory)
+ self._log_optimization_result(success)
+
+ return self.prompt
+
+ async def _handle_first_round(self, prompt_path: Path, data: List[dict]) -> None:
+ logger.info("\n⚡ RUNNING Round 1 PROMPT ⚡\n")
+ directory = self.prompt_utils.create_round_directory(prompt_path, self.round)
+
+ prompt, _, _, _ = load.load_meta_data()
+ self.prompt = prompt
+ self.prompt_utils.write_prompt(directory, prompt=self.prompt)
+
+ new_samples = await self.evaluation_utils.execute_prompt(self, directory)
+ _, answers = await self.evaluation_utils.evaluate_prompt(
+ self, None, new_samples, path=prompt_path, data=data, initial=True
+ )
+ self.prompt_utils.write_answers(directory, answers=answers)
+
+ async def _generate_optimized_prompt(self):
+ _, requirements, qa, count = load.load_meta_data()
+ samples = self.data_utils.get_best_round()
+
+ logger.info(f"\n🚀Round {self.round} OPTIMIZATION STARTING 🚀\n")
+ logger.info(f"\nSelecting prompt for round {samples['round']} and advancing to the iteration phase\n")
+
+ golden_answer = self.data_utils.list_to_markdown(qa)
+ best_answer = self.data_utils.list_to_markdown(samples["answers"])
+
+ optimize_prompt = PROMPT_OPTIMIZE_PROMPT.format(
+ prompt=samples["prompt"],
+ answers=best_answer,
+ requirements=requirements,
+ golden_answers=golden_answer,
+ count=count,
+ )
+
+ response = await self.llm.responser(
+ request_type=RequestType.OPTIMIZE, messages=[{"role": "user", "content": optimize_prompt}]
+ )
+
+ modification = extract_content(response, "modification")
+ logger.info(f"Modification of {self.round} round: {modification}")
+
+ prompt = extract_content(response, "prompt")
+ return prompt if prompt else ""
+
+ async def _evaluate_new_prompt(self, prompt_path, data, directory):
+ logger.info("\n⚡ RUNNING OPTIMIZED PROMPT ⚡\n")
+ new_samples = await self.evaluation_utils.execute_prompt(self, directory)
+
+ logger.info("\n📊 EVALUATING OPTIMIZED PROMPT 📊\n")
+ samples = self.data_utils.get_best_round()
+ success, answers = await self.evaluation_utils.evaluate_prompt(
+ self, samples, new_samples, path=prompt_path, data=data, initial=False
+ )
+
+ self.prompt_utils.write_answers(directory, answers=answers)
+ return success, answers
+
+ def _log_optimization_result(self, success):
+ logger.info("\n🎯 OPTIMIZATION RESULT 🎯\n")
+ logger.info(f"\nRound {self.round} Optimization: {'✅ SUCCESS' if success else '❌ FAILED'}\n")
diff --git a/metagpt/ext/spo/prompts/evaluate_prompt.py b/metagpt/ext/spo/prompts/evaluate_prompt.py
new file mode 100644
index 000000000..80a9b093b
--- /dev/null
+++ b/metagpt/ext/spo/prompts/evaluate_prompt.py
@@ -0,0 +1,20 @@
+EVALUATE_PROMPT = """
+Based on the original requirements, evaluate the two responses, A and B, and determine which one better meets the requirements. If a reference answer is provided, strictly follow the format/content of the reference answer.
+
+# Requirement
+{requirement}
+
+# A
+{sample}
+
+# B
+{new_sample}
+
+# Golden answer
+{answers}
+
+Provide your analysis and the choice you believe is better, using XML tags to encapsulate your response.
+
+Some analysis
+A/B (the better answer in your opinion)
+"""
diff --git a/metagpt/ext/spo/prompts/optimize_prompt.py b/metagpt/ext/spo/prompts/optimize_prompt.py
new file mode 100644
index 000000000..f6ca81e33
--- /dev/null
+++ b/metagpt/ext/spo/prompts/optimize_prompt.py
@@ -0,0 +1,32 @@
+PROMPT_OPTIMIZE_PROMPT = """
+You are building a prompt to address user requirement. Based on the given prompt,
+please reconstruct and optimize it. You can add, modify, or delete prompts. Please include a single modification in
+XML tags in your reply. During the optimization, you can incorporate any thinking models.
+This is a prompt that performed excellently in a previous iteration. You must make further optimizations and improvements based on this prompt. The modified prompt must differ from the provided example.
+
+requirements:
+```
+{requirements}
+```
+
+reference prompt:
+```
+{prompt}
+```
+
+The execution result of this reference prompt is(some cases):
+```
+{answers}
+```
+
+The best answer we expect(some cases):
+```
+{golden_answers}
+```
+
+Provide your analysis, optimization points, and the complete optimized prompt using the following XML format:
+
+Analyze what drawbacks exist in the results produced by the reference prompt and how to improve them.
+Summarize the key points for improvement in one sentence
+Provide the complete optimized prompt {count}
+"""
diff --git a/metagpt/ext/spo/settings/Navigate.yaml b/metagpt/ext/spo/settings/Navigate.yaml
new file mode 100644
index 000000000..3b20a6de9
--- /dev/null
+++ b/metagpt/ext/spo/settings/Navigate.yaml
@@ -0,0 +1,47 @@
+prompt: |
+ Please think step by step.
+ Ensure the response concludes with the answer in the XML format:
+ [Yes or No].
+
+requirements: |
+ Must put the final answer at the end with XML. ((Yes or No),such as Yes)
+ The provided prompt needs to adapt to all current types of questions.
+
+count: None
+
+qa:
+ - question: |
+ If you follow these instructions, do you return to the starting point? Always face forward. Take 7 steps left. Take 2 steps backward. Take 7 steps backward. Take 7 steps backward. Take 3 steps forward.
+ Options:
+ - Yes
+ - No
+
+ answer: |
+ A lot of thinking and analysis processes.
+ ...
+ Final Answer:
+ (Yes or No)
+
+ - question: |
+ If you follow these instructions, do you return to the starting point? Always face forward. Take 6 steps backward. Take 8 steps left. Take 3 steps right. Take 7 steps forward. Take 3 steps right. Take 9 steps right. Take 1 step backward. Take 7 steps left.
+ Options:
+ - Yes
+ - No
+
+ answer: |
+ A lot of thinking and analysis processes.
+ ...
+ Final Answer:
+ (Yes or No)
+
+ - question: |
+ If you follow these instructions, do you return to the starting point? Turn left. Turn left. Take 6 steps. Take 3 steps. Turn around. Take 1 step. Take 3 steps. Take 5 steps.
+ Options:
+ - Yes
+ - No
+
+ answer: |
+ A lot of thinking and analysis processes.
+ ...
+ Final Answer:
+ (Yes or No)
diff --git a/metagpt/ext/spo/settings/Poem.yaml b/metagpt/ext/spo/settings/Poem.yaml
new file mode 100644
index 000000000..dba690c45
--- /dev/null
+++ b/metagpt/ext/spo/settings/Poem.yaml
@@ -0,0 +1,23 @@
+prompt: |
+ Create poetry in the requested style and format.
+
+requirements: |
+ None
+
+count: None
+
+qa:
+ - question: |
+ Write a modern sonnet about climate change
+ answer: |
+ None
+
+ - question: |
+ Create a haiku series about New York City
+ answer: |
+ None
+
+ - question: |
+ Write a free verse poem about social media
+ answer: |
+ None
diff --git a/metagpt/ext/spo/utils/__init__.py b/metagpt/ext/spo/utils/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/metagpt/ext/spo/utils/data_utils.py b/metagpt/ext/spo/utils/data_utils.py
new file mode 100644
index 000000000..17771c021
--- /dev/null
+++ b/metagpt/ext/spo/utils/data_utils.py
@@ -0,0 +1,106 @@
+import datetime
+import json
+from pathlib import Path
+from typing import Dict, List, Union
+
+import pandas as pd
+
+from metagpt.logs import logger
+
+
+class DataUtils:
+ def __init__(self, root_path: Path):
+ self.root_path = root_path
+ self.top_scores = []
+
+ def load_results(self, path: Path) -> list:
+ result_path = self.get_results_file_path(path)
+ if result_path.exists():
+ try:
+ return json.loads(result_path.read_text())
+ except json.JSONDecodeError:
+ return []
+ return []
+
+ def get_best_round(self):
+ self._load_scores()
+
+ for entry in self.top_scores:
+ if entry["succeed"]:
+ return entry
+
+ return None
+
+ def get_results_file_path(self, prompt_path: Path) -> Path:
+ return prompt_path / "results.json"
+
+ def create_result_data(self, round: int, answers: list[dict], prompt: str, succeed: bool, tokens: int) -> dict:
+ now = datetime.datetime.now()
+ return {"round": round, "answers": answers, "prompt": prompt, "succeed": succeed, "tokens": tokens, "time": now}
+
+ def save_results(self, json_file_path: Path, data: Union[List, Dict]):
+ json_path = json_file_path
+ json_path.write_text(json.dumps(data, default=str, indent=4))
+
+ def _load_scores(self):
+ rounds_dir = self.root_path / "prompts"
+ result_file = rounds_dir / "results.json"
+ self.top_scores = []
+
+ try:
+ if not result_file.exists():
+ logger.warning(f"Results file not found at {result_file}")
+ return self.top_scores
+
+ data = json.loads(result_file.read_text(encoding="utf-8"))
+ df = pd.DataFrame(data)
+
+ for index, row in df.iterrows():
+ self.top_scores.append(
+ {
+ "round": row["round"],
+ "succeed": row["succeed"],
+ "prompt": row["prompt"],
+ "answers": row["answers"],
+ }
+ )
+
+ self.top_scores.sort(key=lambda x: x["round"], reverse=True)
+
+ except FileNotFoundError:
+ logger.error(f"Could not find results file: {result_file}")
+ except json.JSONDecodeError:
+ logger.error(f"Invalid JSON format in file: {result_file}")
+ except Exception as e:
+ logger.error(f"Unexpected error loading scores: {str(e)}")
+
+ return self.top_scores
+
+ def list_to_markdown(self, questions_list: list):
+ """
+ Convert a list of question-answer dictionaries to a formatted Markdown string.
+
+ Args:
+ questions_list (list): List of dictionaries containing 'question' and 'answer' keys
+
+ Returns:
+ str: Formatted Markdown string
+ """
+ markdown_text = "```\n"
+
+ for i, qa_pair in enumerate(questions_list, 1):
+ # Add question section
+ markdown_text += f"Question {i}\n\n"
+ markdown_text += f"{qa_pair['question']}\n\n"
+
+ # Add answer section
+ markdown_text += f"Answer {i}\n\n"
+ markdown_text += f"{qa_pair['answer']}\n\n"
+
+ # Add separator between QA pairs except for the last one
+ if i < len(questions_list):
+ markdown_text += "---\n\n"
+
+ markdown_text += "\n```"
+
+ return markdown_text
diff --git a/metagpt/ext/spo/utils/evaluation_utils.py b/metagpt/ext/spo/utils/evaluation_utils.py
new file mode 100644
index 000000000..3fb026a21
--- /dev/null
+++ b/metagpt/ext/spo/utils/evaluation_utils.py
@@ -0,0 +1,81 @@
+import asyncio
+from pathlib import Path
+from typing import Any, List, Optional, Tuple
+
+import tiktoken
+
+from metagpt.ext.spo.components.evaluator import QuickEvaluate, QuickExecute
+from metagpt.logs import logger
+
+EVALUATION_REPETITION = 4
+
+
+def count_tokens(sample: dict):
+ if not sample:
+ return 0
+ else:
+ encoding = tiktoken.get_encoding("cl100k_base")
+ return len(encoding.encode(str(sample["answers"])))
+
+
+class EvaluationUtils:
+ def __init__(self, root_path: Path) -> None:
+ self.root_path = root_path
+
+ async def execute_prompt(self, optimizer: Any, prompt_path: Path) -> dict:
+ optimizer.prompt = optimizer.prompt_utils.load_prompt(optimizer.round, prompt_path)
+ executor = QuickExecute(prompt=optimizer.prompt)
+
+ answers = await executor.prompt_execute()
+
+ cur_round = optimizer.round
+
+ new_data = {"round": cur_round, "answers": answers, "prompt": optimizer.prompt}
+
+ return new_data
+
+ async def evaluate_prompt(
+ self,
+ optimizer: Any,
+ samples: Optional[dict],
+ new_samples: dict,
+ path: Path,
+ data: List[dict],
+ initial: bool = False,
+ ) -> Tuple[bool, dict]:
+ evaluator = QuickEvaluate()
+ new_token = count_tokens(new_samples)
+
+ if initial is True:
+ succeed = True
+ else:
+ evaluation_results = []
+
+ evaluation_results.extend(
+ await asyncio.gather(
+ *(
+ evaluator.prompt_evaluate(samples=samples, new_samples=new_samples)
+ for _ in range(EVALUATION_REPETITION)
+ )
+ )
+ )
+
+ logger.info(f"Evaluation Results {evaluation_results}")
+
+ true_count = evaluation_results.count(True)
+ false_count = evaluation_results.count(False)
+ succeed = true_count > false_count
+
+ new_data = optimizer.data_utils.create_result_data(
+ new_samples["round"], new_samples["answers"], new_samples["prompt"], succeed, new_token
+ )
+
+ data.append(new_data)
+
+ result_path = optimizer.data_utils.get_results_file_path(path)
+
+ optimizer.data_utils.save_results(result_path, data)
+
+ answers = new_samples["answers"]
+
+ return succeed, answers
diff --git a/metagpt/ext/spo/utils/llm_client.py b/metagpt/ext/spo/utils/llm_client.py
new file mode 100644
index 000000000..81524d3c1
--- /dev/null
+++ b/metagpt/ext/spo/utils/llm_client.py
@@ -0,0 +1,107 @@
+import asyncio
+import re
+from enum import Enum
+from typing import Any, List, Optional
+
+from metagpt.configs.models_config import ModelsConfig
+from metagpt.llm import LLM
+from metagpt.logs import logger
+
+
+class RequestType(Enum):
+ OPTIMIZE = "optimize"
+ EVALUATE = "evaluate"
+ EXECUTE = "execute"
+
+
+class SPO_LLM:
+ _instance: Optional["SPO_LLM"] = None
+
+ def __init__(
+ self,
+ optimize_kwargs: Optional[dict] = None,
+ evaluate_kwargs: Optional[dict] = None,
+ execute_kwargs: Optional[dict] = None,
+ ) -> None:
+ self.evaluate_llm = LLM(llm_config=self._load_llm_config(evaluate_kwargs))
+ self.optimize_llm = LLM(llm_config=self._load_llm_config(optimize_kwargs))
+ self.execute_llm = LLM(llm_config=self._load_llm_config(execute_kwargs))
+
+ def _load_llm_config(self, kwargs: dict) -> Any:
+ model = kwargs.get("model")
+ if not model:
+ raise ValueError("'model' parameter is required")
+
+ try:
+ model_config = ModelsConfig.default().get(model)
+ if model_config is None:
+ raise ValueError(f"Model '{model}' not found in configuration")
+
+ config = model_config.model_copy()
+
+ for key, value in kwargs.items():
+ if hasattr(config, key):
+ setattr(config, key, value)
+
+ return config
+
+ except AttributeError:
+ raise ValueError(f"Model '{model}' not found in configuration")
+ except Exception as e:
+ raise ValueError(f"Error loading configuration for model '{model}': {str(e)}")
+
+ async def responser(self, request_type: RequestType, messages: List[dict]) -> str:
+ llm_mapping = {
+ RequestType.OPTIMIZE: self.optimize_llm,
+ RequestType.EVALUATE: self.evaluate_llm,
+ RequestType.EXECUTE: self.execute_llm,
+ }
+
+ llm = llm_mapping.get(request_type)
+ if not llm:
+ raise ValueError(f"Invalid request type. Valid types: {', '.join([t.value for t in RequestType])}")
+
+ response = await llm.acompletion(messages)
+ return response.choices[0].message.content
+
+ @classmethod
+ def initialize(cls, optimize_kwargs: dict, evaluate_kwargs: dict, execute_kwargs: dict) -> None:
+ """Initialize the global instance"""
+ cls._instance = cls(optimize_kwargs, evaluate_kwargs, execute_kwargs)
+
+ @classmethod
+ def get_instance(cls) -> "SPO_LLM":
+ """Get the global instance"""
+ if cls._instance is None:
+ raise RuntimeError("SPO_LLM not initialized. Call initialize() first.")
+ return cls._instance
+
+
+def extract_content(xml_string: str, tag: str) -> Optional[str]:
+ pattern = rf"<{tag}>(.*?){tag}>"
+ match = re.search(pattern, xml_string, re.DOTALL)
+ return match.group(1).strip() if match else None
+
+
+async def main():
+ # test LLM
+ SPO_LLM.initialize(
+ optimize_kwargs={"model": "gpt-4o", "temperature": 0.7},
+ evaluate_kwargs={"model": "gpt-4o-mini", "temperature": 0.3},
+ execute_kwargs={"model": "gpt-4o-mini", "temperature": 0.3},
+ )
+
+ llm = SPO_LLM.get_instance()
+
+ # test messages
+ hello_msg = [{"role": "user", "content": "hello"}]
+ response = await llm.responser(request_type=RequestType.EXECUTE, messages=hello_msg)
+ logger(f"AI: {response}")
+ response = await llm.responser(request_type=RequestType.OPTIMIZE, messages=hello_msg)
+ logger(f"AI: {response}")
+ response = await llm.responser(request_type=RequestType.EVALUATE, messages=hello_msg)
+ logger(f"AI: {response}")
+
+
+if __name__ == "__main__":
+ asyncio.run(main())
diff --git a/metagpt/ext/spo/utils/load.py b/metagpt/ext/spo/utils/load.py
new file mode 100644
index 000000000..6333b2775
--- /dev/null
+++ b/metagpt/ext/spo/utils/load.py
@@ -0,0 +1,48 @@
+import random
+from pathlib import Path
+
+import yaml
+
+FILE_NAME = ""
+SAMPLE_K = 3
+
+
+def set_file_name(name: str):
+ global FILE_NAME
+ FILE_NAME = name
+
+
+def load_meta_data(k: int = SAMPLE_K):
+ # load yaml file
+ config_path = Path(__file__).parent.parent / "settings" / FILE_NAME
+
+ if not config_path.exists():
+ raise FileNotFoundError(f"Configuration file '{FILE_NAME}' not found in settings directory")
+
+ try:
+ with config_path.open("r", encoding="utf-8") as file:
+ data = yaml.safe_load(file)
+ except yaml.YAMLError as e:
+ raise ValueError(f"Error parsing YAML file '{FILE_NAME}': {str(e)}")
+ except Exception as e:
+ raise Exception(f"Error reading file '{FILE_NAME}': {str(e)}")
+
+ qa = []
+
+ for item in data["qa"]:
+ question = item["question"]
+ answer = item["answer"]
+ qa.append({"question": question, "answer": answer})
+
+ prompt = data["prompt"]
+ requirements = data["requirements"]
+ count = data["count"]
+
+ if isinstance(count, int):
+ count = f", within {count} words"
+ else:
+ count = ""
+
+ random_qa = random.sample(qa, min(k, len(qa)))
+
+ return prompt, requirements, random_qa, count
diff --git a/metagpt/ext/spo/utils/prompt_utils.py b/metagpt/ext/spo/utils/prompt_utils.py
new file mode 100644
index 000000000..c1c960bb7
--- /dev/null
+++ b/metagpt/ext/spo/utils/prompt_utils.py
@@ -0,0 +1,34 @@
+from pathlib import Path
+
+from metagpt.logs import logger
+
+
+class PromptUtils:
+ def __init__(self, root_path: Path):
+ self.root_path = root_path
+
+ def create_round_directory(self, prompt_path: Path, round_number: int) -> Path:
+ directory = prompt_path / f"round_{round_number}"
+ directory.mkdir(parents=True, exist_ok=True)
+ return directory
+
+ def load_prompt(self, round_number: int, prompts_path: Path):
+ prompt_file = prompts_path / "prompt.txt"
+
+ try:
+ return prompt_file.read_text(encoding="utf-8")
+ except FileNotFoundError as e:
+ logger.info(f"Error loading prompt for round {round_number}: {e}")
+ raise
+
+ def write_answers(self, directory: Path, answers: dict, name: str = "answers.txt"):
+ answers_file = directory / name
+ with answers_file.open("w", encoding="utf-8") as file:
+ for item in answers:
+ file.write(f"Question:\n{item['question']}\n")
+ file.write(f"Answer:\n{item['answer']}\n")
+ file.write("\n")
+
+ def write_prompt(self, directory: Path, prompt: str):
+ prompt_file = directory / "prompt.txt"
+ prompt_file.write_text(prompt, encoding="utf-8")
diff --git a/metagpt/ext/stanford_town/README.md b/metagpt/ext/stanford_town/README.md
index bfcb1c818..1bdcac145 100644
--- a/metagpt/ext/stanford_town/README.md
+++ b/metagpt/ext/stanford_town/README.md
@@ -32,5 +32,20 @@ ### Frontend service startup
Enter `environment/frontend_server` and use `python3 manage.py runserver` to start the front-end service.
Visit `http://localhost:8000/simulator_home` to enter the current simulation interface.
-## Appreciation
-The reproduction work has referred the `https://github.com/joonspk-research/generative_agents`, let's make a general statement here.
+## Acknowledgements
+The reproduction work has referred the [generative_agents](https://github.com/joonspk-research/generative_agents), let's make a general statement here.
+
+### Citation
+```bib
+@inproceedings{Park2023GenerativeAgents,
+author = {Park, Joon Sung and O'Brien, Joseph C. and Cai, Carrie J. and Morris, Meredith Ringel and Liang, Percy and Bernstein, Michael S.},
+title = {Generative Agents: Interactive Simulacra of Human Behavior},
+year = {2023},
+publisher = {Association for Computing Machinery},
+address = {New York, NY, USA},
+booktitle = {In the 36th Annual ACM Symposium on User Interface Software and Technology (UIST '23)},
+keywords = {Human-AI interaction, agents, generative AI, large language models},
+location = {San Francisco, CA, USA},
+series = {UIST '23}
+}
+```
\ No newline at end of file
diff --git a/metagpt/ext/stanford_town/README_CN.md b/metagpt/ext/stanford_town/README_CN.md
index c55a742fc..3daf68d08 100644
--- a/metagpt/ext/stanford_town/README_CN.md
+++ b/metagpt/ext/stanford_town/README_CN.md
@@ -31,5 +31,20 @@ ### 前端服务启动
进入`environment/frontend_server`,使用`python3 manage.py runserver`启动前端服务。
访问`http://localhost:8000/simulator_home` 进入当前的仿真界面。
-## Appreciation
-The reproduction work has referred the `https://github.com/joonspk-research/generative_agents`, let's make a general statement here.
+## 致谢
+复现工作参考了 [generative_agents](https://github.com/joonspk-research/generative_agents), 感谢相关作者们。
+
+### 引用
+```bib
+@inproceedings{Park2023GenerativeAgents,
+author = {Park, Joon Sung and O'Brien, Joseph C. and Cai, Carrie J. and Morris, Meredith Ringel and Liang, Percy and Bernstein, Michael S.},
+title = {Generative Agents: Interactive Simulacra of Human Behavior},
+year = {2023},
+publisher = {Association for Computing Machinery},
+address = {New York, NY, USA},
+booktitle = {In the 36th Annual ACM Symposium on User Interface Software and Technology (UIST '23)},
+keywords = {Human-AI interaction, agents, generative AI, large language models},
+location = {San Francisco, CA, USA},
+series = {UIST '23}
+}
+```
diff --git a/metagpt/ext/stanford_town/roles/st_role.py b/metagpt/ext/stanford_town/roles/st_role.py
index 592b78a8f..679a32674 100644
--- a/metagpt/ext/stanford_town/roles/st_role.py
+++ b/metagpt/ext/stanford_town/roles/st_role.py
@@ -254,7 +254,7 @@ class STRole(Role):
# We will order our percept based on the distance, with the closest ones
# getting priorities.
percept_events_list = []
- # First, we put all events that are occuring in the nearby tiles into the
+ # First, we put all events that are occurring in the nearby tiles into the
# percept_events_list
for tile in nearby_tiles:
tile_details = self.rc.env.observe(EnvObsParams(obs_type=EnvObsType.GET_TITLE, coord=tile))
diff --git a/metagpt/ext/stanford_town/utils/mg_ga_transform.py b/metagpt/ext/stanford_town/utils/mg_ga_transform.py
index e0de76a6b..da1b31a4a 100644
--- a/metagpt/ext/stanford_town/utils/mg_ga_transform.py
+++ b/metagpt/ext/stanford_town/utils/mg_ga_transform.py
@@ -56,10 +56,16 @@ def get_role_environment(sim_code: str, role_name: str, step: int = 0) -> dict:
def write_curr_sim_code(curr_sim_code: dict, temp_storage_path: Optional[Path] = None):
- temp_storage_path = Path(temp_storage_path) or TEMP_STORAGE_PATH
+ if temp_storage_path is None:
+ temp_storage_path = TEMP_STORAGE_PATH
+ else:
+ temp_storage_path = Path(temp_storage_path)
write_json_file(temp_storage_path.joinpath("curr_sim_code.json"), curr_sim_code)
def write_curr_step(curr_step: dict, temp_storage_path: Optional[Path] = None):
- temp_storage_path = Path(temp_storage_path) or TEMP_STORAGE_PATH
+ if temp_storage_path is None:
+ temp_storage_path = TEMP_STORAGE_PATH
+ else:
+ temp_storage_path = Path(temp_storage_path)
write_json_file(temp_storage_path.joinpath("curr_step.json"), curr_step)
diff --git a/metagpt/ext/stanford_town/utils/utils.py b/metagpt/ext/stanford_town/utils/utils.py
index e09cce8fe..b4e15f485 100644
--- a/metagpt/ext/stanford_town/utils/utils.py
+++ b/metagpt/ext/stanford_town/utils/utils.py
@@ -50,6 +50,7 @@ def read_csv_to_list(curr_file: str, header=False, strip_trail=True):
def get_embedding(text, model: str = "text-embedding-ada-002"):
config = Config.default()
text = text.replace("\n", " ")
+ embedding = None
if not text:
text = "this is blank"
for idx in range(3):
@@ -57,7 +58,8 @@ def get_embedding(text, model: str = "text-embedding-ada-002"):
embedding = (
OpenAI(api_key=config.llm.api_key).embeddings.create(input=[text], model=model).data[0].embedding
)
- except Exception:
+ except Exception as exp:
+ logger.info(f"get_embedding failed, exp: {exp}, will retry.")
time.sleep(5)
if not embedding:
raise ValueError("get_embedding failed")
diff --git a/metagpt/ext/werewolf/__init__.py b/metagpt/ext/werewolf/__init__.py
new file mode 100644
index 000000000..2bcf8efd0
--- /dev/null
+++ b/metagpt/ext/werewolf/__init__.py
@@ -0,0 +1,3 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# @Desc :
diff --git a/metagpt/ext/werewolf/actions/__init__.py b/metagpt/ext/werewolf/actions/__init__.py
new file mode 100644
index 000000000..c994c43c8
--- /dev/null
+++ b/metagpt/ext/werewolf/actions/__init__.py
@@ -0,0 +1,23 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# @Desc :
+
+from metagpt.ext.werewolf.actions.werewolf_actions import Hunt, Impersonate
+from metagpt.ext.werewolf.actions.guard_actions import Protect
+from metagpt.ext.werewolf.actions.seer_actions import Verify
+from metagpt.ext.werewolf.actions.witch_actions import Save, Poison
+from metagpt.ext.werewolf.actions.common_actions import Speak, NighttimeWhispers, Reflect
+from metagpt.ext.werewolf.actions.experience_operation import AddNewExperiences, RetrieveExperiences
+from metagpt.ext.werewolf.actions.moderator_actions import InstructSpeak
+
+ACTIONS = {
+ "Speak": Speak,
+ "Hunt": Hunt,
+ "Protect": Protect,
+ "Verify": Verify,
+ "Save": Save,
+ "Poison": Poison,
+ "Impersonate": Impersonate,
+}
+
+__all__ = ["NighttimeWhispers", "Reflect", "AddNewExperiences", "RetrieveExperiences", "InstructSpeak"]
diff --git a/metagpt/ext/werewolf/actions/common_actions.py b/metagpt/ext/werewolf/actions/common_actions.py
new file mode 100644
index 000000000..63afeede0
--- /dev/null
+++ b/metagpt/ext/werewolf/actions/common_actions.py
@@ -0,0 +1,240 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# @Desc :
+
+import json
+
+from tenacity import retry, stop_after_attempt, wait_fixed
+
+from metagpt.actions import Action
+from metagpt.logs import logger
+from metagpt.utils.common import parse_json_code_block
+
+
+def log_and_parse_json(name: str, rsp: str) -> dict:
+ rsp = rsp.replace("\n", " ")
+ logger.debug(f"{name} result: {rsp}")
+ json_blocks = parse_json_code_block(rsp)
+ rsp_json = json.loads(json_blocks[0])
+ return rsp_json
+
+
+class Speak(Action):
+ """Action: Any speak action in a game"""
+
+ PROMPT_TEMPLATE: str = """
+ {
+ "BACKGROUND": "It's a Werewolf game, in this game, we have 2 werewolves, 2 villagers, 1 guard, 1 witch, 1 seer. You are __profile__. Note that villager, seer, guard and witch are all in villager side, they have the same objective. Werewolves can collectively hunt ONE player at night."
+ ,"HISTORY": "You have knowledge to the following conversation: __context__"
+ ,"ATTENTION": "You can NOT VOTE a player who is NOT ALIVE now!"
+ ,"REFLECTION": "__reflection__"
+ ,"STRATEGY": __strategy__
+ ,"PAST_EXPERIENCES": "__experiences__"
+ ,"MODERATOR_INSTRUCTION": __latest_instruction__,
+ ,"RULE": "Please follow the moderator's latest instruction, figure out if you need to speak your opinion or directly to vote:
+ 1. If the instruction is to SPEAK, speak in 200 words. Remember the goal of your role and try to achieve it using your speech;
+ 2. If the instruction is to VOTE, you MUST vote and ONLY say 'I vote to eliminate PlayerX', replace PlayerX with the actual player name, DO NOT include any other words."
+ ,"OUTPUT_FORMAT":
+ {
+ "ROLE": "Your role, in this case, __profile__"
+ ,"PLAYER_NAME": "Your name, in this case, __name__"
+ ,"LIVING_PLAYERS": "List living players based on MODERATOR_INSTRUCTION. Return a json LIST datatype."
+ ,"THOUGHTS": "Based on `MODERATOR_INSTRUCTION` and `RULE`, carefully think about what to say or vote so that your chance of win as __profile__ maximizes.
+ If you find similar situation in `PAST_EXPERIENCES`, you may draw lessons from them to refine your strategy, take better vote action, or improve your speech.
+ Give your step-by-step thought process, you should think no more than 3 steps. For example: My step-by-step thought process:..."
+ ,"RESPONSE": "Based on `MODERATOR_INSTRUCTION`, `RULE`, and the 'THOUGHTS' you had, express your opinion or cast a vote."
+ }
+ }
+ """
+ STRATEGY: str = """
+ Decide whether to reveal your identity based on benefits vs. risks, provide useful information, and vote to eliminate the most suspicious.
+ If you have special abilities, pay attention to those who falsely claims your role, for they are probably werewolves.
+ """
+
+ name: str = "Speak"
+
+ @retry(stop=stop_after_attempt(2), wait=wait_fixed(1))
+ async def run(
+ self,
+ profile: str,
+ name: str,
+ context: str,
+ latest_instruction: str,
+ reflection: str = "",
+ experiences: str = "",
+ ):
+ prompt = (
+ self.PROMPT_TEMPLATE.replace("__context__", context)
+ .replace("__profile__", profile)
+ .replace("__name__", name)
+ .replace("__latest_instruction__", latest_instruction)
+ .replace("__strategy__", self.STRATEGY)
+ .replace("__reflection__", reflection)
+ .replace("__experiences__", experiences)
+ )
+
+ rsp = await self._aask(prompt)
+ rsp_json = log_and_parse_json(self.name, rsp)
+
+ return rsp_json["RESPONSE"]
+
+
+class NighttimeWhispers(Action):
+ """
+
+ Action: nighttime whispers with thinking processes
+
+ Usage Example:
+
+ class Hunt(NighttimeWhispers):
+ def __init__(self, name="Hunt", context=None, llm=None):
+ super().__init__(name, context, llm)
+
+ class Protect(NighttimeWhispers):
+ def __init__(self, name="Protect", context=None, llm=None):
+ super().__init__(name, context, llm)
+
+ class Verify(NighttimeWhispers):
+ def __init__(self, name="Verify", context=None, llm=None):
+ super().__init__(name, context, llm)
+
+ class Save(NighttimeWhispers):
+ def __init__(self, name="Save", context=None, llm=None):
+ super().__init__(name, context, llm)
+
+ def _update_prompt_json(self, prompt_json: dict, profile: str, name: str, context: str, **kwargs):
+ del prompt_json['ACTION']
+ del prompt_json['ATTENTION']
+ prompt_json["OUTPUT_FORMAT"]["THOUGHTS"] = "It is night time. Return the thinking steps of your decision of whether to save the player JUST be killed at this night."
+ prompt_json["OUTPUT_FORMAT"]["RESPONSE"] = "Follow the Moderator's instruction, decide whether you want to save that person or not. Return SAVE or PASS."
+ return prompt_json
+
+ class Poison(NighttimeWhispers):
+ def __init__(self, name="Poison", context=None, llm=None):
+ super().__init__(name, context, llm)
+
+ def _update_prompt_json(self, prompt_json: dict, profile: str, name: str, context: str, **kwargs):
+ prompt_json["OUTPUT_FORMAT"]["RESPONSE"] += "Or if you want to PASS, return PASS."
+ return prompt_json
+ """
+
+ PROMPT_TEMPLATE: str = """
+ {
+ "BACKGROUND": "It's a Werewolf game, in this game, we have 2 werewolves, 2 villagers, 1 guard, 1 witch, 1 seer. You are __profile__. Note that villager, seer, guard and witch are all in villager side, they have the same objective. Werewolves can collectively hunt ONE player at night."
+ ,"HISTORY": "You have knowledge to the following conversation: __context__"
+ ,"ACTION": "Choose one living player to __action__."
+ ,"ATTENTION": "1. You can only __action__ a player who is alive this night! And you can not __action__ a player who is dead this night! 2. `HISTORY` is all the information you observed, DONT hallucinate other player actions!"
+ ,"REFLECTION": "__reflection__"
+ ,"STRATEGY": "__strategy__"
+ ,"PAST_EXPERIENCES": "__experiences__"
+ ,"OUTPUT_FORMAT":
+ {
+ "ROLE": "Your role, in this case, __profile__"
+ ,"PLAYER_NAME": "Your name, in this case, __name__"
+ ,"LIVING_PLAYERS": "List the players who is alive based on moderator's latest instruction. Return a json LIST datatype."
+ ,"THOUGHTS": "Choose one living player from `LIVING_PLAYERS` to __action__ this night. Return the reason why you choose to __action__ this player. If you observe nothing at first night, DONT imagine unexisting player actions! If you find similar situation in `PAST_EXPERIENCES`, you may draw lessons from them to refine your strategy and take better actions. Give your step-by-step thought process, you should think no more than 3 steps. For example: My step-by-step thought process:..."
+ ,"RESPONSE": "As a __profile__, you should choose one living player from `LIVING_PLAYERS` to __action__ this night according to the THOUGHTS you have just now. Return the player name ONLY."
+ }
+ }
+ """
+ STRATEGY: str = """
+ Decide which player is most threatening to you or most needs your support, take your action correspondingly.
+ """
+
+ name: str = "NightTimeWhispers"
+
+ def _construct_prompt_json(
+ self, role_profile: str, role_name: str, context: str, reflection: str, experiences: str, **kwargs
+ ):
+ prompt_template = self.PROMPT_TEMPLATE
+
+ def replace_string(prompt_json: dict):
+ k: str
+ for k in prompt_json.keys():
+ if isinstance(prompt_json[k], dict):
+ prompt_json[k] = replace_string(prompt_json[k])
+ continue
+ prompt_json[k] = prompt_json[k].replace("__profile__", role_profile)
+ prompt_json[k] = prompt_json[k].replace("__name__", role_name)
+ prompt_json[k] = prompt_json[k].replace("__context__", context)
+ prompt_json[k] = prompt_json[k].replace("__action__", self.name)
+ prompt_json[k] = prompt_json[k].replace("__strategy__", self.STRATEGY)
+ prompt_json[k] = prompt_json[k].replace("__reflection__", reflection)
+ prompt_json[k] = prompt_json[k].replace("__experiences__", experiences)
+
+ return prompt_json
+
+ prompt_json: dict = json.loads(prompt_template)
+
+ prompt_json = replace_string(prompt_json)
+
+ prompt_json: dict = self._update_prompt_json(
+ prompt_json, role_profile, role_name, context, reflection, experiences, **kwargs
+ )
+ assert isinstance(prompt_json, dict)
+
+ prompt: str = json.dumps(prompt_json, indent=4, ensure_ascii=False)
+
+ return prompt
+
+ def _update_prompt_json(
+ self, prompt_json: dict, role_profile: str, role_name: str, context: str, reflection: str, experiences: str
+ ) -> dict:
+ # one can modify the prompt_json dictionary here
+ return prompt_json
+
+ @retry(stop=stop_after_attempt(2), wait=wait_fixed(1))
+ async def run(self, context: str, profile: str, name: str, reflection: str = "", experiences: str = ""):
+ prompt = self._construct_prompt_json(
+ role_profile=profile, role_name=name, context=context, reflection=reflection, experiences=experiences
+ )
+
+ rsp = await self._aask(prompt)
+ rsp_json = log_and_parse_json(self.name, rsp)
+
+ return f"{self.name} " + rsp_json["RESPONSE"]
+
+
+class Reflect(Action):
+ PROMPT_TEMPLATE: str = """
+ {
+ "BACKGROUND": "It's a Werewolf game, in this game, we have 2 werewolves, 2 villagers, 1 guard, 1 witch, 1 seer. You are __profile__. Note that villager, seer, guard and witch are all in villager side, they have the same objective. Werewolves can collectively hunt ONE player at night."
+ ,"HISTORY": "You have knowledge to the following conversation: __context__"
+ ,"MODERATOR_INSTRUCTION": __latest_instruction__,
+ ,"OUTPUT_FORMAT" (a json):
+ {
+ "ROLE": "Your role, in this case, __profile__"
+ ,"PLAYER_NAME": "Your name, in this case, __name__"
+ "GAME_STATES": "You are about to follow `MODERATOR_INSTRUCTION`, but before taking any action, analyze each player, including the living and the dead, and summarize the game states.
+ For each player, your reflection should be a ONE-LINE json covering the following dimension, return a LIST of jsons (return an empty LIST for the first night):
+ [
+ {"TARGET": "the player you will analyze, if the player is yourself or your werewolf partner, indicate it" ,"STATUS": "living or dead, if dead, how was he/she possibly killed?", "CLAIMED_ROLE": "claims a role or not, if so, what role, any contradiction to others? If there is no claim, return 'None'", "SIDE_WITH": "sides with which players? If none, return 'None'", "ACCUSE": "accuses which players? If none, return 'None'"}
+ ,{...}
+ ,...
+ ]"
+ ,"REFLECTION": "Based on the whole `GAME_STATES`, return a json (return an empty string for the first night):
+ {
+ "Player1": "the true role (werewolf / special role / villager, living or dead) you infer about him/her, and why is this role? If the player is yourself or your werewolf partner, indicate it."
+ ,...
+ ,"Player7": "the true role (werewolf / special role / villager, living or dead) you infer about him/her, and why is this role? If the player is yourself or your werewolf partner, indicate it."
+ ,"GAME_STATE_SUMMARIZATION": "summarize the current situation from your standpoint in one sentence, your summarization should catch the most important information from your reflection, such as conflicts, number of living werewolves, special roles, and villagers."
+ }"
+ }
+ }
+ """
+
+ name: str = "Reflect"
+
+ @retry(stop=stop_after_attempt(2), wait=wait_fixed(1))
+ async def run(self, profile: str, name: str, context: str, latest_instruction: str):
+ prompt = (
+ self.PROMPT_TEMPLATE.replace("__context__", context)
+ .replace("__profile__", profile)
+ .replace("__name__", name)
+ .replace("__latest_instruction__", latest_instruction)
+ )
+
+ rsp = await self._aask(prompt)
+ rsp_json = log_and_parse_json(self.name, rsp)
+
+ return json.dumps(rsp_json["REFLECTION"])
diff --git a/metagpt/ext/werewolf/actions/experience_operation.py b/metagpt/ext/werewolf/actions/experience_operation.py
new file mode 100644
index 000000000..1f2e491d6
--- /dev/null
+++ b/metagpt/ext/werewolf/actions/experience_operation.py
@@ -0,0 +1,162 @@
+import json
+from typing import Optional
+
+import chromadb
+from pydantic import model_validator
+
+from metagpt.actions import Action
+from metagpt.const import DEFAULT_WORKSPACE_ROOT
+from metagpt.environment.werewolf.const import RoleType
+from metagpt.ext.werewolf.schema import RoleExperience
+from metagpt.logs import logger
+from metagpt.rag.engines.simple import SimpleEngine
+from metagpt.rag.schema import ChromaIndexConfig, ChromaRetrieverConfig
+from metagpt.utils.common import read_json_file, write_json_file
+
+DEFAULT_COLLECTION_NAME = "role_reflection" # FIXME: some hard code for now
+PERSIST_PATH = DEFAULT_WORKSPACE_ROOT.joinpath("werewolf_game/chroma")
+PERSIST_PATH.mkdir(parents=True, exist_ok=True)
+
+
+class AddNewExperiences(Action):
+ name: str = "AddNewExperience"
+ collection_name: str = DEFAULT_COLLECTION_NAME
+ delete_existing: bool = False
+ engine: Optional[SimpleEngine] = None
+
+ @model_validator(mode="after")
+ def validate_collection(self):
+ if self.engine:
+ return
+ if self.delete_existing:
+ try:
+ # implement engine `DELETE` method later
+ chromadb.PersistentClient(PERSIST_PATH.as_posix()).delete_collection(self.collection_name)
+ except Exception as exp:
+ logger.error(f"delete chroma collection: {self.collection_name} failed, exp: {exp}")
+
+ self.engine = SimpleEngine.from_objs(
+ retriever_configs=[
+ ChromaRetrieverConfig(
+ persist_path=PERSIST_PATH, collection_name=self.collection_name, metadata={"hnsw:space": "cosine"}
+ )
+ ]
+ )
+
+ def run(self, experiences: list[RoleExperience]):
+ if not experiences:
+ return
+ for i, exp in enumerate(experiences):
+ exp.id = f"{exp.profile}-{exp.name}-step{i}-round_{exp.round_id}"
+
+ AddNewExperiences._record_experiences_local(experiences)
+
+ self.engine.add_objs(experiences)
+
+ def add_from_file(self, file_path):
+ experiences = read_json_file(file_path)
+ experiences = [RoleExperience.model_validate(item) for item in experiences]
+ experiences = [exp for exp in experiences if len(exp.reflection) > 2] # not "" or not '""'
+
+ self.engine.add_objs(experiences)
+
+ @staticmethod
+ def _record_experiences_local(experiences: list[RoleExperience]):
+ round_id = experiences[0].round_id
+ version = experiences[0].version
+ version = "test" if not version else version
+ experiences = [exp.model_dump() for exp in experiences]
+
+ experience_path = DEFAULT_WORKSPACE_ROOT.joinpath(f"werewolf_game/experiences/{version}")
+ experience_path.mkdir(parents=True, exist_ok=True)
+ save_path = f"{experience_path}/{round_id}.json"
+ write_json_file(save_path, experiences)
+ logger.info(f"experiences saved to {save_path}")
+
+
+class RetrieveExperiences(Action):
+ name: str = "RetrieveExperiences"
+ collection_name: str = DEFAULT_COLLECTION_NAME
+ has_experiences: bool = True
+ engine: Optional[SimpleEngine] = None
+ topk: int = 10
+
+ @model_validator(mode="after")
+ def validate_collection(self):
+ if self.engine:
+ return
+ try:
+ self.engine = SimpleEngine.from_index(
+ index_config=ChromaIndexConfig(
+ persist_path=PERSIST_PATH, collection_name=self.collection_name, metadata={"hnsw:space": "cosine"}
+ ),
+ retriever_configs=[
+ ChromaRetrieverConfig(
+ similarity_top_k=self.topk,
+ persist_path=PERSIST_PATH,
+ collection_name=self.collection_name,
+ metadata={"hnsw:space": "cosine"},
+ )
+ ],
+ )
+ except Exception as exp:
+ logger.warning(f"No experience pool: {self.collection_name}, exp: {exp}")
+
+ def run(self, query: str, profile: str, excluded_version: str = "", verbose: bool = False) -> str:
+ """_summary_
+
+ Args:
+ query (str): 用当前的reflection作为query去检索过去相似的reflection
+ profile (str): _description_
+
+ Returns:
+ _type_: _description_
+ """
+ if not self.engine or len(query) <= 2: # not "" or not '""'
+ logger.warning("engine is None or query too short")
+ return ""
+
+ # ablation experiment logic
+ if profile == RoleType.WEREWOLF.value: # role werewolf as baseline, don't use experiences
+ logger.warning("Disable werewolves' experiences")
+ return ""
+
+ results = self.engine.retrieve(query)
+
+ logger.info(f"retrieve {profile}'s experiences")
+ experiences = [res.metadata["obj"] for res in results]
+
+ past_experiences = [] # currently use post-process to filter, and later add `filters` in rag
+ for exp in experiences:
+ if exp.profile == profile and exp.version != excluded_version:
+ past_experiences.append(exp)
+
+ if verbose and results:
+ logger.info("past_experiences: {}".format("\n\n".join(past_experiences)))
+ distances = results[0].score
+ logger.info(f"distances: {distances}")
+
+ template = """
+ {
+ "Situation __i__": "__situation__"
+ ,"Moderator's instruction": "__instruction__"
+ ,"Your action or speech during that time": "__response__"
+ ,"Reality": "In fact, it turned out the true roles are __game_step__",
+ ,"Outcome": "You __outcome__ in the end"
+ }
+ """
+ past_experiences = [
+ (
+ template.replace("__i__", str(i))
+ .replace("__situation__", exp.reflection)
+ .replace("__instruction__", exp.instruction)
+ .replace("__response__", exp.response)
+ .replace("__game_step__", exp.game_setup.replace("0 | Game setup:\n", "").replace("\n", " "))
+ .replace("__outcome__", exp.outcome)
+ )
+ for i, exp in enumerate(past_experiences)
+ ]
+ logger.info("past_experiences: {}".format("\n".join(past_experiences)))
+ logger.info("retrieval done")
+
+ return json.dumps(past_experiences)
diff --git a/metagpt/ext/werewolf/actions/guard_actions.py b/metagpt/ext/werewolf/actions/guard_actions.py
new file mode 100644
index 000000000..fbacd9aeb
--- /dev/null
+++ b/metagpt/ext/werewolf/actions/guard_actions.py
@@ -0,0 +1,9 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# @Desc :
+
+from metagpt.ext.werewolf.actions.common_actions import NighttimeWhispers
+
+
+class Protect(NighttimeWhispers):
+ name: str = "Protect"
diff --git a/metagpt/ext/werewolf/actions/moderator_actions.py b/metagpt/ext/werewolf/actions/moderator_actions.py
new file mode 100644
index 000000000..ba5d13e64
--- /dev/null
+++ b/metagpt/ext/werewolf/actions/moderator_actions.py
@@ -0,0 +1,39 @@
+from metagpt.actions import Action
+from metagpt.environment.werewolf.const import STEP_INSTRUCTIONS
+
+
+class InstructSpeak(Action):
+ name: str = "InstructSpeak"
+
+ async def run(self, step_idx, living_players, werewolf_players, player_hunted, player_current_dead):
+ instruction_info = STEP_INSTRUCTIONS.get(
+ step_idx, {"content": "Unknown instruction.", "send_to": {}, "restricted_to": {}}
+ )
+ content = instruction_info["content"]
+ if "{living_players}" in content and "{werewolf_players}" in content:
+ content = content.format(
+ living_players=living_players, werewolf_players=werewolf_players, werewolf_num=len(werewolf_players)
+ )
+ if "{living_players}" in content:
+ content = content.format(living_players=living_players)
+ if "{werewolf_players}" in content:
+ content = content.format(werewolf_players=werewolf_players)
+ if "{player_hunted}" in content:
+ content = content.format(player_hunted=player_hunted)
+ if "{player_current_dead}" in content:
+ player_current_dead = "No one" if not player_current_dead else player_current_dead
+ content = content.format(player_current_dead=player_current_dead)
+
+ return content, instruction_info["send_to"], instruction_info["restricted_to"]
+
+
+class ParseSpeak(Action):
+ name: str = "ParseSpeak"
+
+ async def run(self):
+ pass
+
+
+class AnnounceGameResult(Action):
+ async def run(self, winner: str, win_reason: str):
+ return f"Game over! {win_reason}. The winner is the {winner}"
diff --git a/metagpt/ext/werewolf/actions/seer_actions.py b/metagpt/ext/werewolf/actions/seer_actions.py
new file mode 100644
index 000000000..a2c4977c5
--- /dev/null
+++ b/metagpt/ext/werewolf/actions/seer_actions.py
@@ -0,0 +1,5 @@
+from metagpt.ext.werewolf.actions.common_actions import NighttimeWhispers
+
+
+class Verify(NighttimeWhispers):
+ name: str = "Verify"
diff --git a/metagpt/ext/werewolf/actions/werewolf_actions.py b/metagpt/ext/werewolf/actions/werewolf_actions.py
new file mode 100644
index 000000000..33dfd44b1
--- /dev/null
+++ b/metagpt/ext/werewolf/actions/werewolf_actions.py
@@ -0,0 +1,17 @@
+from metagpt.ext.werewolf.actions.common_actions import NighttimeWhispers, Speak
+
+
+class Hunt(NighttimeWhispers):
+ name: str = "Hunt"
+
+
+class Impersonate(Speak):
+ """Action: werewolf impersonating a good guy in daytime speak"""
+
+ STRATEGY: str = """
+ Try continuously impersonating a role, such as Seer, Guard, Villager, etc., in order to mislead
+ other players, make them trust you, and thus hiding your werewolf identity. However, pay attention to what your werewolf partner said,
+ DONT claim the same role as your werewolf partner. Remmber NOT to reveal your real identity as a werewolf!
+ """
+
+ name: str = "Impersonate"
diff --git a/metagpt/ext/werewolf/actions/witch_actions.py b/metagpt/ext/werewolf/actions/witch_actions.py
new file mode 100644
index 000000000..20a0568c5
--- /dev/null
+++ b/metagpt/ext/werewolf/actions/witch_actions.py
@@ -0,0 +1,47 @@
+from metagpt.environment.werewolf.const import RoleActionRes
+from metagpt.ext.werewolf.actions.common_actions import NighttimeWhispers
+
+
+class Save(NighttimeWhispers):
+ name: str = "Save"
+
+ def _update_prompt_json(
+ self, prompt_json: dict, role_profile: str, role_name: str, context: str, reflection: str, experiences: str
+ ) -> dict:
+ del prompt_json["ACTION"]
+ del prompt_json["ATTENTION"]
+
+ prompt_json["OUTPUT_FORMAT"][
+ "THOUGHTS"
+ ] = "It is night time. Return the thinking steps of your decision of whether to save the player JUST killed this night."
+ prompt_json["OUTPUT_FORMAT"][
+ "RESPONSE"
+ ] = "Follow the Moderator's instruction, decide whether you want to save that person or not. Return SAVE or PASS."
+
+ return prompt_json
+
+ async def run(self, *args, **kwargs):
+ rsp = await super().run(*args, **kwargs)
+ action_name, rsp = rsp.split()
+ return rsp # 只需回复SAVE或PASS,不需要带上action名
+
+
+class Poison(NighttimeWhispers):
+ STRATEGY: str = """
+ Only poison a player if you are confident he/she is a werewolf. Don't poison a player randomly or at first night.
+ If someone claims to be the witch, poison him/her, because you are the only witch, he/she can only be a werewolf.
+ """
+
+ name: str = "Poison"
+
+ def _update_prompt_json(
+ self, prompt_json: dict, role_profile: str, role_name: str, context: str, reflection: str, experiences: str
+ ) -> dict:
+ prompt_json["OUTPUT_FORMAT"]["RESPONSE"] += "Or if you want to PASS, return PASS."
+ return prompt_json
+
+ async def run(self, *args, **kwargs):
+ rsp = await super().run(*args, **kwargs)
+ if RoleActionRes.PASS.value in rsp.lower():
+ action_name, rsp = rsp.split() # 带PASS,只需回复PASS,不需要带上action名,否则是Poison PlayerX,无需改动
+ return rsp
diff --git a/metagpt/ext/werewolf/roles/__init__.py b/metagpt/ext/werewolf/roles/__init__.py
new file mode 100644
index 000000000..fa9e3a642
--- /dev/null
+++ b/metagpt/ext/werewolf/roles/__init__.py
@@ -0,0 +1,13 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# @Desc :
+
+from metagpt.ext.werewolf.roles.base_player import BasePlayer
+from metagpt.ext.werewolf.roles.guard import Guard
+from metagpt.ext.werewolf.roles.seer import Seer
+from metagpt.ext.werewolf.roles.villager import Villager
+from metagpt.ext.werewolf.roles.werewolf import Werewolf
+from metagpt.ext.werewolf.roles.witch import Witch
+from metagpt.ext.werewolf.roles.moderator import Moderator
+
+__all__ = ["BasePlayer", "Guard", "Moderator", "Seer", "Villager", "Witch", "Werewolf"]
diff --git a/metagpt/ext/werewolf/roles/base_player.py b/metagpt/ext/werewolf/roles/base_player.py
new file mode 100644
index 000000000..a108718e0
--- /dev/null
+++ b/metagpt/ext/werewolf/roles/base_player.py
@@ -0,0 +1,177 @@
+import re
+
+from pydantic import Field, SerializeAsAny, model_validator
+
+from metagpt.actions.action import Action
+from metagpt.environment.werewolf.const import RoleState, RoleType
+from metagpt.ext.werewolf.actions import (
+ ACTIONS,
+ AddNewExperiences,
+ InstructSpeak,
+ NighttimeWhispers,
+ Reflect,
+ RetrieveExperiences,
+ Speak,
+)
+from metagpt.ext.werewolf.schema import RoleExperience, WwMessage
+from metagpt.logs import logger
+from metagpt.roles import Role
+from metagpt.utils.common import any_to_str
+
+
+class BasePlayer(Role):
+ name: str = "PlayerXYZ"
+ profile: str = "BasePlayer"
+ special_action_names: list[str] = []
+ use_reflection: bool = True
+ use_experience: bool = False
+ use_memory_selection: bool = False
+ new_experience_version: str = ""
+ status: RoleState = RoleState.ALIVE
+
+ special_actions: list[SerializeAsAny[Action]] = Field(default=[], validate_default=True)
+ experiences: list[RoleExperience] = []
+
+ def __init__(self, **kwargs):
+ super().__init__(**kwargs)
+ # 技能和监听配置
+ self._watch([InstructSpeak]) # 监听Moderator的指令以做行动
+ special_actions = [ACTIONS[action_name] for action_name in self.special_action_names]
+ capable_actions = [Speak] + special_actions
+ self.set_actions(capable_actions) # 给角色赋予行动技能
+ self.special_actions = special_actions
+
+ if not self.use_reflection and self.use_experience:
+ logger.warning("You must enable use_reflection before using experience")
+ self.use_experience = False
+
+ @model_validator(mode="after")
+ def check_addresses(self):
+ if not self.addresses:
+ self.addresses = {any_to_str(self), self.name, self.profile} if self.name else {any_to_str(self)}
+ return self
+
+ async def _observe(self, ignore_memory=False) -> int:
+ if self.status != RoleState.ALIVE:
+ # 死者不再参与游戏
+ return 0
+ news = []
+ if not news:
+ news = self.rc.msg_buffer.pop_all()
+ old_messages = [] if ignore_memory else self.rc.memory.get()
+ for m in news:
+ if len(m.restricted_to) and self.profile not in m.restricted_to and self.name not in m.restricted_to:
+ # if the msg is not send to the whole audience ("") nor this role (self.profile or self.name),
+ # then this role should not be able to receive it and record it into its memory
+ continue
+ self.rc.memory.add(m)
+ self.rc.news = [
+ n for n in news if (n.cause_by in self.rc.watch or self.profile in n.send_to) and n not in old_messages
+ ]
+
+ # TODO to delete
+ # await super()._observe()
+ # # 只有发给全体的("")或发给自己的(self.profile)消息需要走下面的_react流程,
+ # # 其他的收听到即可,不用做动作
+ # self.rc.news = [msg for msg in self.rc.news if msg.send_to in ["", self.profile]]
+ return len(self.rc.news)
+
+ async def _think(self):
+ news = self.rc.news[0]
+ assert news.cause_by == any_to_str(InstructSpeak) # 消息为来自Moderator的指令时,才去做动作
+ if not news.restricted_to:
+ # 消息接收范围为全体角色的,做公开发言(发表投票观点也算发言)
+ self.rc.todo = Speak()
+ elif self.profile in news.restricted_to:
+ # FIXME: hard code to split, restricted为"Moderator"或"Moderator, 角色profile"
+ # Moderator加密发给自己的,意味着要执行角色的特殊动作
+ self.rc.todo = self.special_actions[0]()
+ return True
+
+ async def _act(self):
+ # todo为_think时确定的,有两种情况,Speak或Protect
+ todo = self.rc.todo
+ logger.info(f"{self._setting}: ready to {str(todo)}")
+
+ # 可以用这个函数获取该角色的全部记忆和最新的instruction
+ memories = self.get_all_memories()
+ latest_instruction = self.get_latest_instruction()
+
+ reflection = (
+ await Reflect().run(
+ profile=self.profile, name=self.name, context=memories, latest_instruction=latest_instruction
+ )
+ if self.use_reflection
+ else ""
+ )
+
+ experiences = (
+ RetrieveExperiences().run(
+ query=reflection, profile=self.profile, excluded_version=self.new_experience_version
+ )
+ if self.use_experience
+ else ""
+ )
+
+ # 根据自己定义的角色Action,对应地去run,run的入参可能不同
+ if isinstance(todo, Speak):
+ rsp = await todo.run(
+ profile=self.profile,
+ name=self.name,
+ context=memories,
+ latest_instruction=latest_instruction,
+ reflection=reflection,
+ experiences=experiences,
+ )
+ restricted_to = set()
+
+ elif isinstance(todo, NighttimeWhispers):
+ rsp = await todo.run(
+ profile=self.profile, name=self.name, context=memories, reflection=reflection, experiences=experiences
+ )
+ restricted_to = {RoleType.MODERATOR.value, self.profile} # 给Moderator发送使用特殊技能的加密消息
+ msg = WwMessage(
+ content=rsp,
+ role=self.profile,
+ sent_from=self.name,
+ cause_by=type(todo),
+ send_to={},
+ restricted_to=restricted_to,
+ )
+
+ self.experiences.append(
+ RoleExperience(
+ name=self.name,
+ profile=self.profile,
+ reflection=reflection,
+ instruction=latest_instruction,
+ response=rsp,
+ version=self.new_experience_version,
+ )
+ )
+
+ logger.info(f"{self._setting}: {rsp}")
+
+ return msg
+
+ def get_all_memories(self) -> str:
+ memories = self.rc.memory.get()
+ time_stamp_pattern = r"[0-9]+ \| "
+ # NOTE: 除Moderator外,其他角色使用memory,只能用m.sent_from(玩家名)不能用m.role(玩家角色),因为他们不知道说话者的身份
+ memories = [f"{m.sent_from}: {re.sub(time_stamp_pattern, '', m.content)}" for m in memories] # regex去掉时间戳
+ memories = "\n".join(memories)
+ return memories
+
+ def get_latest_instruction(self) -> str:
+ return self.rc.important_memory[-1].content # 角色监听着Moderator的InstructSpeak,是其重要记忆,直接获取即可
+
+ def set_status(self, new_status: RoleState):
+ self.status = new_status
+
+ def record_experiences(self, round_id: str, outcome: str, game_setup: str):
+ experiences = [exp for exp in self.experiences if len(exp.reflection) > 2] # not "" or not '""'
+ for exp in experiences:
+ exp.round_id = round_id
+ exp.outcome = outcome
+ exp.game_setup = game_setup
+ AddNewExperiences().run(experiences)
diff --git a/metagpt/ext/werewolf/roles/guard.py b/metagpt/ext/werewolf/roles/guard.py
new file mode 100644
index 000000000..a78ee61ef
--- /dev/null
+++ b/metagpt/ext/werewolf/roles/guard.py
@@ -0,0 +1,8 @@
+from metagpt.environment.werewolf.const import RoleType
+from metagpt.ext.werewolf.roles.base_player import BasePlayer
+
+
+class Guard(BasePlayer):
+ name: str = RoleType.GUARD.value
+ profile: str = RoleType.GUARD.value
+ special_action_names: list[str] = ["Protect"]
diff --git a/metagpt/ext/werewolf/roles/human_player.py b/metagpt/ext/werewolf/roles/human_player.py
new file mode 100644
index 000000000..4a31dd549
--- /dev/null
+++ b/metagpt/ext/werewolf/roles/human_player.py
@@ -0,0 +1,45 @@
+from metagpt.environment.werewolf.const import RoleType
+from metagpt.ext.werewolf.actions import Speak
+from metagpt.ext.werewolf.roles import BasePlayer
+from metagpt.ext.werewolf.schema import WwMessage
+from metagpt.logs import logger
+
+
+async def _act(self):
+ todo = self.rc.todo
+
+ memories = self.get_all_memories()
+
+ input_instruction = f"""
+ ## As a reminder, you have access to the following game history:
+ {memories}
+ ## You are {self.name}({self.profile})
+ ## Guidance:
+ 1. If you are performing a special action or exercising a vote,
+ end your response with "PlayerX", replace PlayerX with the actual player name, e.g., "..., kill/protect/poison/.../vote Player1".
+ 2. If it is a daytime free speech, you can speak in whatever format.
+ Now, please speak:
+ """
+ rsp = input(input_instruction) # wait for human input
+
+ msg_cause_by = type(todo)
+ msg_restricted_to = {} if isinstance(todo, Speak) else {RoleType.MODERATOR.value, self.profile}
+
+ msg = WwMessage(
+ content=rsp,
+ role=self.profile,
+ sent_from=self.name,
+ cause_by=msg_cause_by,
+ send_to={},
+ restricted_to=msg_restricted_to, # 给Moderator及自身阵营发送加密消息
+ )
+
+ logger.info(f"{self._setting}: {rsp}")
+
+ return msg
+
+
+def prepare_human_player(player_class: BasePlayer):
+ # Dynamically define a human player class that inherits from a certain role class
+ HumanPlayer = type("HumanPlayer", (player_class,), {"_act": _act})
+ return HumanPlayer
diff --git a/metagpt/ext/werewolf/roles/moderator.py b/metagpt/ext/werewolf/roles/moderator.py
new file mode 100644
index 000000000..9621d9acb
--- /dev/null
+++ b/metagpt/ext/werewolf/roles/moderator.py
@@ -0,0 +1,252 @@
+import re
+from datetime import datetime
+from typing import Union
+
+from metagpt.actions.add_requirement import UserRequirement
+from metagpt.const import DEFAULT_WORKSPACE_ROOT, MESSAGE_ROUTE_TO_ALL
+from metagpt.environment.werewolf.const import (
+ STEP_INSTRUCTIONS,
+ RoleActionRes,
+ RoleState,
+ RoleType,
+)
+from metagpt.environment.werewolf.env_space import EnvAction, EnvActionType
+from metagpt.ext.werewolf.actions import Hunt, Poison, Protect, Save, Verify
+from metagpt.ext.werewolf.actions.moderator_actions import (
+ AnnounceGameResult,
+ InstructSpeak,
+ ParseSpeak,
+)
+from metagpt.ext.werewolf.roles.base_player import BasePlayer
+from metagpt.ext.werewolf.schema import WwMessage
+from metagpt.logs import logger
+from metagpt.utils.common import any_to_str
+
+
+class Moderator(BasePlayer):
+ name: str = RoleType.MODERATOR.value
+ profile: str = RoleType.MODERATOR.value
+
+ def __init__(self, **kwargs):
+ super().__init__(**kwargs)
+ self._watch([UserRequirement, InstructSpeak, ParseSpeak])
+ self.set_actions([InstructSpeak, ParseSpeak, AnnounceGameResult])
+
+ # game states
+ self.step_idx = 0
+ self.game_setup = ""
+ self.werewolf_players = []
+ self.winner = None
+ self.win_reason = None
+ self.witch_poison_left = 1
+ self.witch_antidote_left = 1
+
+ def update_player_status(self, player_names: list[str]):
+ if not player_names:
+ return
+ roles_in_env = self.rc.env.get_roles()
+ for role_setting, role in roles_in_env.items():
+ for player_name in player_names:
+ if player_name in role_setting:
+ role.set_status(new_status=RoleState.DEAD) # 更新为死亡
+
+ def _record_all_experiences(self):
+ logger.info(f"The winner of the game: {self.winner}, start to record roles' experiences")
+ roles_in_env = self.rc.env.get_roles()
+ timestamp = datetime.now().strftime("%Y_%m_%d_%H_%M_%S")
+ for _, role in roles_in_env.items():
+ if role == self:
+ continue
+ if self.winner == "werewolf":
+ outcome = "won" if role.profile in RoleType.WEREWOLF.value else "lost"
+ else:
+ outcome = "won" if role.profile not in RoleType.WEREWOLF.value else "lost"
+ role.record_experiences(round_id=timestamp, outcome=outcome, game_setup=self.game_setup)
+
+ async def _parse_speak(self, memories):
+ latest_msg = memories[-1]
+ latest_msg_content = latest_msg.content
+
+ match = re.search(r"Player[0-9]+", latest_msg_content[-10:]) # FIXME: hard code truncation
+ target = match.group(0) if match else ""
+
+ # default return
+ msg_content = "Understood"
+ restricted_to = set()
+
+ msg_cause_by = latest_msg.cause_by
+ if msg_cause_by == any_to_str(Hunt):
+ self.rc.env.step(
+ EnvAction(
+ action_type=EnvActionType.WOLF_KILL, player_name=latest_msg.sent_from, target_player_name=target
+ )
+ )
+ elif msg_cause_by == any_to_str(Protect):
+ self.rc.env.step(
+ EnvAction(
+ action_type=EnvActionType.GUARD_PROTECT, player_name=latest_msg.sent_from, target_player_name=target
+ )
+ )
+ elif msg_cause_by == any_to_str(Verify):
+ if target in self.werewolf_players:
+ msg_content = f"{target} is a werewolf"
+ else:
+ msg_content = f"{target} is a good guy"
+ restricted_to = {RoleType.MODERATOR.value, RoleType.SEER.value}
+ elif msg_cause_by == any_to_str(Save):
+ if RoleActionRes.PASS.value in latest_msg_content.lower():
+ # the role ignore to response, answer `pass`
+ pass
+ elif not self.witch_antidote_left:
+ msg_content = "You have no antidote left and thus can not save the player"
+ restricted_to = {RoleType.MODERATOR.value, RoleType.WITCH.value}
+ else:
+ self.rc.env.step(
+ EnvAction(
+ action_type=EnvActionType.WITCH_SAVE,
+ player_name=latest_msg.sent_from,
+ target_player_name=target,
+ )
+ )
+ elif msg_cause_by == any_to_str(Poison):
+ if RoleActionRes.PASS.value in latest_msg_content.lower():
+ pass
+ elif not self.witch_poison_left:
+ msg_content = "You have no poison left and thus can not poison the player"
+ restricted_to = {RoleType.MODERATOR.value, RoleType.WITCH.value}
+ else:
+ self.rc.env.step(
+ EnvAction(
+ action_type=EnvActionType.WITCH_POISON,
+ player_name=latest_msg.sent_from,
+ target_player_name=target,
+ )
+ )
+
+ return msg_content, restricted_to
+
+ def _update_player_status(self, step_idx: int, player_current_dead: list[str]):
+ """update dead player's status"""
+ if step_idx in [15, 18]:
+ self.update_player_status(player_current_dead)
+
+ def _record_game_history(self, step_idx: int):
+ if step_idx and step_idx % len(STEP_INSTRUCTIONS) == 0 or self.winner:
+ logger.info("a night and day cycle completed, examine all history")
+ logger.debug(f"all_memories: {self.get_all_memories()}")
+ with open(DEFAULT_WORKSPACE_ROOT / "werewolf_transcript.txt", "w") as f:
+ f.write(self.get_all_memories())
+
+ async def _observe(self, ignore_memory=False) -> int:
+ news = []
+ if not news:
+ news = self.rc.msg_buffer.pop_all()
+ old_messages = [] if ignore_memory else self.rc.memory.get()
+ for m in news:
+ if len(m.restricted_to) and self.profile not in m.restricted_to and self.name not in m.restricted_to:
+ # if the msg is not send to the whole audience ("") nor this role (self.profile or self.name),
+ # then this role should not be able to receive it and record it into its memory
+ continue
+ self.rc.memory.add(m)
+ # add `MESSAGE_ROUTE_TO_ALL in n.send_to` make it to run `ParseSpeak`
+ self.rc.news = [
+ n
+ for n in news
+ if (n.cause_by in self.rc.watch or self.profile in n.send_to or MESSAGE_ROUTE_TO_ALL in n.send_to)
+ and n not in old_messages
+ ]
+ return len(self.rc.news)
+
+ async def _think(self):
+ if self.winner:
+ self.rc.todo = AnnounceGameResult()
+ return
+
+ latest_msg = self.rc.memory.get()[-1]
+ if latest_msg.role in ["User", "Human", self.profile]:
+ # 1. 上一轮消息是用户指令,解析用户指令,开始游戏
+ # 2.1. 上一轮消息是Moderator自己的指令,继续发出指令,一个事情可以分几条消息来说
+ # 2.2. 上一轮消息是Moderator自己的解析消息,一个阶段结束,发出新一个阶段的指令
+ self.rc.todo = InstructSpeak()
+ else:
+ # 上一轮消息是游戏角色的发言,解析角色的发言
+ self.rc.todo = ParseSpeak()
+ return True
+
+ def _init_fields_from_obj(self, obs: dict[str, Union[int, str, list[str]]]):
+ self.game_setup = obs.get("game_setup", "")
+ self.step_idx = obs.get("step_idx", 0)
+ self.winner = obs.get("winner")
+ self.win_reason = obs.get("win_reason")
+ self.werewolf_players = obs.get("werewolf_players", [])
+ self.witch_poison_left = obs.get("witch_poison_left", 0)
+ self.witch_antidote_left = obs.get("witch_antidote_left", 0)
+
+ async def _act(self):
+ todo = self.rc.todo
+ logger.info(f"{self._setting} ready to {todo}")
+
+ memories = self.get_all_memories(mode="msg")
+
+ obs, _, _, _, _ = self.rc.env.step(action=EnvAction(action_type=EnvActionType.NONE))
+ living_players = obs["living_players"]
+ werewolf_players = obs["werewolf_players"]
+ player_hunted = obs["player_hunted"]
+ player_current_dead = obs["player_current_dead"]
+ self._init_fields_from_obj(obs)
+
+ # 若进行完一夜一日的循环,打印和记录一次完整发言历史
+ self._record_game_history(self.step_idx)
+
+ # 若一晚或一日周期结束,对当晚或当日的死者进行总结,并更新玩家状态
+ self._update_player_status(self.step_idx, player_current_dead)
+ if self.winner:
+ self._record_all_experiences()
+
+ # 根据_think的结果,执行InstructSpeak还是ParseSpeak, 并将结果返回
+ if isinstance(todo, InstructSpeak):
+ msg_content, msg_to_send_to, msg_restricted_to = await InstructSpeak().run(
+ self.step_idx,
+ living_players=living_players,
+ werewolf_players=werewolf_players,
+ player_hunted=player_hunted,
+ player_current_dead=player_current_dead,
+ )
+ # msg_content = f"Step {self.step_idx}: {msg_content}" # HACK: 加一个unique的step_idx避免记忆的自动去重
+ msg = WwMessage(
+ content=msg_content,
+ role=self.profile,
+ sent_from=self.name,
+ cause_by=InstructSpeak,
+ send_to=msg_to_send_to,
+ restricted_to=msg_restricted_to,
+ )
+ logger.info(f"current step_idx: {self.step_idx}")
+ self.rc.env.step(EnvAction(action_type=EnvActionType.PROGRESS_STEP)) # to update step_idx
+
+ elif isinstance(todo, ParseSpeak):
+ msg_content, msg_restricted_to = await self._parse_speak(memories)
+ # msg_content = f"Step {self.step_idx}: {msg_content}" # HACK: 加一个unique的step_idx避免记忆的自动去重
+ msg = WwMessage(
+ content=msg_content,
+ role=self.profile,
+ sent_from=self.name,
+ cause_by=ParseSpeak,
+ send_to={},
+ restricted_to=msg_restricted_to,
+ )
+
+ elif isinstance(todo, AnnounceGameResult):
+ msg_content = await AnnounceGameResult().run(winner=self.winner, win_reason=self.win_reason)
+ msg = WwMessage(content=msg_content, role=self.profile, sent_from=self.name, cause_by=AnnounceGameResult)
+
+ logger.info(f"{self._setting}: {msg_content}")
+
+ return msg
+
+ def get_all_memories(self, mode="str") -> str:
+ memories = self.rc.memory.get()
+ if mode == "str":
+ memories = [f"{m.sent_from}({m.role}): {m.content}" for m in memories]
+ memories = "\n".join(memories)
+ return memories
diff --git a/metagpt/ext/werewolf/roles/seer.py b/metagpt/ext/werewolf/roles/seer.py
new file mode 100644
index 000000000..c669ac28d
--- /dev/null
+++ b/metagpt/ext/werewolf/roles/seer.py
@@ -0,0 +1,8 @@
+from metagpt.environment.werewolf.const import RoleType
+from metagpt.ext.werewolf.roles.base_player import BasePlayer
+
+
+class Seer(BasePlayer):
+ name: str = RoleType.SEER.value
+ profile: str = RoleType.SEER.value
+ special_action_names: list[str] = ["Verify"]
diff --git a/metagpt/ext/werewolf/roles/villager.py b/metagpt/ext/werewolf/roles/villager.py
new file mode 100644
index 000000000..55b048a07
--- /dev/null
+++ b/metagpt/ext/werewolf/roles/villager.py
@@ -0,0 +1,8 @@
+from metagpt.environment.werewolf.const import RoleType
+from metagpt.ext.werewolf.roles.base_player import BasePlayer
+
+
+class Villager(BasePlayer):
+ name: str = RoleType.VILLAGER.value
+ profile: str = RoleType.VILLAGER.value
+ special_action_names: list[str] = []
diff --git a/metagpt/ext/werewolf/roles/werewolf.py b/metagpt/ext/werewolf/roles/werewolf.py
new file mode 100644
index 000000000..8c2ef283a
--- /dev/null
+++ b/metagpt/ext/werewolf/roles/werewolf.py
@@ -0,0 +1,16 @@
+from metagpt.environment.werewolf.const import RoleType
+from metagpt.ext.werewolf.actions import Impersonate, Speak
+from metagpt.ext.werewolf.roles.base_player import BasePlayer
+
+
+class Werewolf(BasePlayer):
+ name: str = RoleType.WEREWOLF.value
+ profile: str = RoleType.WEREWOLF.value
+ special_action_names: list[str] = ["Hunt"]
+
+ async def _think(self):
+ """狼人白天发言时需要伪装,与其他角色不同,因此需要重写_think"""
+ await super()._think()
+ if isinstance(self.rc.todo, Speak):
+ self.rc.todo = Impersonate()
+ return True
diff --git a/metagpt/ext/werewolf/roles/witch.py b/metagpt/ext/werewolf/roles/witch.py
new file mode 100644
index 000000000..34f9c77d2
--- /dev/null
+++ b/metagpt/ext/werewolf/roles/witch.py
@@ -0,0 +1,29 @@
+from metagpt.environment.werewolf.const import RoleType
+from metagpt.ext.werewolf.actions import InstructSpeak, Poison, Save, Speak
+from metagpt.ext.werewolf.roles.base_player import BasePlayer
+from metagpt.utils.common import any_to_str
+
+
+class Witch(BasePlayer):
+ name: str = RoleType.WITCH.value
+ profile: str = RoleType.WITCH.value
+ special_action_names: list[str] = ["Save", "Poison"]
+
+ async def _think(self):
+ """女巫涉及两个特殊技能,因此在此需要改写_think进行路由"""
+ news = self.rc.news[0]
+ assert news.cause_by == any_to_str(InstructSpeak) # 消息为来自Moderator的指令时,才去做动作
+ if not news.restricted_to:
+ # 消息接收范围为全体角色的,做公开发言(发表投票观点也算发言)
+ self.rc.todo = Speak()
+ elif self.profile in news.restricted_to:
+ # FIXME: hard code to split, restricted为"Moderator"或"Moderator,角色profile"
+ # Moderator加密发给自己的,意味着要执行角色的特殊动作
+ # 这里用关键词进行动作的选择,需要Moderator侧的指令进行配合
+ if "save" in news.content.lower():
+ self.rc.todo = Save()
+ elif "poison" in news.content.lower():
+ self.rc.todo = Poison()
+ else:
+ raise ValueError("Moderator's instructions must include save or poison keyword")
+ return True
diff --git a/metagpt/ext/werewolf/schema.py b/metagpt/ext/werewolf/schema.py
new file mode 100644
index 000000000..1502a2391
--- /dev/null
+++ b/metagpt/ext/werewolf/schema.py
@@ -0,0 +1,33 @@
+from typing import Any
+
+from pydantic import BaseModel, Field, field_validator
+
+from metagpt.schema import Message
+from metagpt.utils.common import any_to_str_set
+
+
+class RoleExperience(BaseModel):
+ id: str = ""
+ name: str = ""
+ profile: str
+ reflection: str
+ instruction: str = ""
+ response: str
+ outcome: str = ""
+ round_id: str = ""
+ game_setup: str = ""
+ version: str = ""
+
+ def rag_key(self) -> str:
+ """For search"""
+ return self.reflection
+
+
+class WwMessage(Message):
+ # Werewolf Message
+ restricted_to: set[str] = Field(default=set(), validate_default=True)
+
+ @field_validator("restricted_to", mode="before")
+ @classmethod
+ def check_restricted_to(cls, restricted_to: Any):
+ return any_to_str_set(restricted_to if restricted_to else set())
diff --git a/metagpt/ext/werewolf/werewolf_game.py b/metagpt/ext/werewolf/werewolf_game.py
new file mode 100644
index 000000000..4deb831a0
--- /dev/null
+++ b/metagpt/ext/werewolf/werewolf_game.py
@@ -0,0 +1,28 @@
+from typing import Any, Optional
+
+from metagpt.actions.add_requirement import UserRequirement
+from metagpt.context import Context
+from metagpt.environment.werewolf.werewolf_env import WerewolfEnv
+from metagpt.ext.werewolf.schema import WwMessage
+from metagpt.team import Team
+
+
+class WerewolfGame(Team):
+ """Use the "software company paradigm" to hold a werewolf game"""
+
+ env: Optional[WerewolfEnv] = None
+
+ def __init__(self, context: Context = None, **data: Any):
+ super(Team, self).__init__(**data)
+ ctx = context or Context()
+ if not self.env:
+ self.env = WerewolfEnv(context=ctx)
+ else:
+ self.env.context = ctx # The `env` object is allocated by deserialization
+
+ def run_project(self, idea):
+ """Run a project from user instruction."""
+ self.idea = idea
+ self.env.publish_message(
+ WwMessage(role="User", content=idea, cause_by=UserRequirement, restricted_to={"Moderator"})
+ )
diff --git a/metagpt/logs.py b/metagpt/logs.py
index 63c10fa2f..ce2f12e4c 100644
--- a/metagpt/logs.py
+++ b/metagpt/logs.py
@@ -34,9 +34,14 @@ TOOL_LOG_END_MARKER = ToolLogItem(
type="str", name="end_marker", value="\x18\x19\x1B\x18"
) # A special log item to suggest the end of a stream log
+_print_level = "INFO"
+
def define_log_level(print_level="INFO", logfile_level="DEBUG", name: str = None):
"""Adjust the log level to above level"""
+ global _print_level
+ _print_level = print_level
+
current_date = datetime.now()
formatted_date = current_date.strftime("%Y%m%d")
log_name = f"{name}_{formatted_date}" if name else formatted_date # name a log with prefix name
@@ -141,3 +146,8 @@ def get_llm_stream_queue():
_get_human_input = input # get human input from console by default
+
+
+def _llm_stream_log(msg):
+ if _print_level in ["INFO"]:
+ print(msg, end="")
diff --git a/metagpt/memory/memory.py b/metagpt/memory/memory.py
index 0707a36ea..5dbadf9c3 100644
--- a/metagpt/memory/memory.py
+++ b/metagpt/memory/memory.py
@@ -82,7 +82,7 @@ class Memory(BaseModel):
return self.storage[-k:]
def find_news(self, observed: list[Message], k=0) -> list[Message]:
- """find news (previously unseen messages) from the the most recent k memories, from all memories when k=0"""
+ """find news (previously unseen messages) from the most recent k memories, from all memories when k=0"""
already_observed = self.get(k)
news: list[Message] = []
for i in observed:
diff --git a/metagpt/prompts/di/write_analysis_code.py b/metagpt/prompts/di/write_analysis_code.py
index 55d5e77cd..300d06c9b 100644
--- a/metagpt/prompts/di/write_analysis_code.py
+++ b/metagpt/prompts/di/write_analysis_code.py
@@ -47,17 +47,19 @@ def add(a: int, b: int) -> int:
user:
Tests failed:
assert add(1, 2) == 3 # output: -1
-assert add(1, 2) == 4 # output: -1
+assert add(1, 3) == 4 # output: -2
-[reflection on previous impl]:
+[reflection on previous impl]
The 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.
-[improved impl]:
+[improved impl]
+```python
def add(a: int, b: int) -> int:
"""
Given integers a and b, return the total value of a and b.
"""
return a + b
+```
'''
REFLECTION_PROMPT = """
@@ -69,17 +71,17 @@ Here is an example of debugging with reflection.
[context]
{context}
-[previous impl]:
+[previous impl]
{previous_impl}
[instruction]
Analyze your previous code and error in [context] step by step, provide me with improved method and code. Remember to follow [context] requirement. Don't forget to write code for steps behind the error step.
-Output a json following the format:
-```json
-{{
- "reflection": str = "Reflection on previous implementation",
- "improved_impl": str = "Refined code after reflection.",
-}}
+Output in the following format:
+[reflection on previous impl]
+...
+[improved impl]:
+```python
+# your code
```
"""
diff --git a/metagpt/prompts/task_type.py b/metagpt/prompts/task_type.py
index 3aa4f5ed4..40d8f4924 100644
--- a/metagpt/prompts/task_type.py
+++ b/metagpt/prompts/task_type.py
@@ -11,7 +11,7 @@ The current task is about data preprocessing, please note the following:
- Monitor data types per column, applying appropriate methods.
- Ensure operations are on existing dataset columns.
- Avoid writing processed data to files.
-- Avoid any change to label column, such as standardization, etc.
+- **ATTENTION** Do NOT make any changes to the label column, such as standardization, etc.
- Prefer alternatives to one-hot encoding for categorical data.
- Only encode or scale necessary columns to allow for potential feature-specific engineering tasks (like time_extract, binning, extraction, etc.) later.
- Each step do data preprocessing to train, must do same for test separately at the same time.
@@ -25,8 +25,8 @@ The current task is about feature engineering. when performing it, please adhere
- Use available feature engineering tools if they are potential impactful.
- Avoid creating redundant or excessively numerous features in one step.
- Exclude ID columns from feature generation and remove them.
-- Each feature engineering operation performed on the train set must also applies to the test separately at the same time.
-- Avoid using the label column to create features, except for cat encoding.
+- Each feature engineering operation performed on the train set must also applies to the dev/test separately at the same time.
+- **ATTENTION** Do NOT use the label column to create features, except for cat encoding.
- Use the data from previous task result if exist, do not mock or reload data yourself.
- Always copy the DataFrame before processing it and use the copy to process.
"""
@@ -34,6 +34,10 @@ The current task is about feature engineering. when performing it, please adhere
# Prompt for taking on "model_train" tasks
MODEL_TRAIN_PROMPT = """
The current task is about training a model, please ensure high performance:
+- For tabular datasets - you have access to XGBoost, CatBoost, random forest, extremely randomized trees, k-nearest neighbors, linear regression, etc.
+- For image datasets - you have access to Swin Transformer, ViT, ResNet, EfficientNet, etc.
+- For text datasets - you have access to Electra, DeBERTa, GPT-2, BERT, etc.
+- Avoid the use of SVM because of its high training time.
- 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.
diff --git a/metagpt/provider/__init__.py b/metagpt/provider/__init__.py
index 14d5e7682..c90f5774a 100644
--- a/metagpt/provider/__init__.py
+++ b/metagpt/provider/__init__.py
@@ -17,6 +17,8 @@ from metagpt.provider.spark_api import SparkLLM
from metagpt.provider.qianfan_api import QianFanLLM
from metagpt.provider.dashscope_api import DashScopeLLM
from metagpt.provider.anthropic_api import AnthropicLLM
+from metagpt.provider.bedrock_api import BedrockLLM
+from metagpt.provider.ark_api import ArkLLM
__all__ = [
"GeminiLLM",
@@ -30,4 +32,6 @@ __all__ = [
"QianFanLLM",
"DashScopeLLM",
"AnthropicLLM",
+ "BedrockLLM",
+ "ArkLLM",
]
diff --git a/metagpt/provider/ark_api.py b/metagpt/provider/ark_api.py
new file mode 100644
index 000000000..0c5704b91
--- /dev/null
+++ b/metagpt/provider/ark_api.py
@@ -0,0 +1,99 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+"""
+Provider for volcengine.
+See Also: https://console.volcengine.com/ark/region:ark+cn-beijing/model
+
+config2.yaml example:
+```yaml
+llm:
+ base_url: "https://ark.cn-beijing.volces.com/api/v3"
+ api_type: "ark"
+ endpoint: "ep-2024080514****-d****"
+ api_key: "d47****b-****-****-****-d6e****0fd77"
+ pricing_plan: "doubao-lite"
+```
+"""
+from typing import Optional, Union
+
+from pydantic import BaseModel
+from volcenginesdkarkruntime import AsyncArk
+from volcenginesdkarkruntime._base_client import AsyncHttpxClientWrapper
+from volcenginesdkarkruntime._streaming import AsyncStream
+from volcenginesdkarkruntime.types.chat import ChatCompletion, ChatCompletionChunk
+
+from metagpt.configs.llm_config import LLMType
+from metagpt.const import USE_CONFIG_TIMEOUT
+from metagpt.logs import log_llm_stream
+from metagpt.provider.llm_provider_registry import register_provider
+from metagpt.provider.openai_api import OpenAILLM
+from metagpt.utils.token_counter import DOUBAO_TOKEN_COSTS
+
+
+@register_provider(LLMType.ARK)
+class ArkLLM(OpenAILLM):
+ """
+ 用于火山方舟的API
+ 见:https://www.volcengine.com/docs/82379/1263482
+ """
+
+ aclient: Optional[AsyncArk] = None
+
+ def _init_client(self):
+ """SDK: https://github.com/openai/openai-python#async-usage"""
+ self.model = (
+ self.config.endpoint or self.config.model
+ ) # endpoint name, See more: https://console.volcengine.com/ark/region:ark+cn-beijing/endpoint
+ self.pricing_plan = self.config.pricing_plan or self.model
+ kwargs = self._make_client_kwargs()
+ self.aclient = AsyncArk(**kwargs)
+
+ def _make_client_kwargs(self) -> dict:
+ kvs = {
+ "ak": self.config.access_key,
+ "sk": self.config.secret_key,
+ "api_key": self.config.api_key,
+ "base_url": self.config.base_url,
+ }
+ kwargs = {k: v for k, v in kvs.items() if v}
+
+ # to use proxy, openai v1 needs http_client
+ if proxy_params := self._get_proxy_params():
+ kwargs["http_client"] = AsyncHttpxClientWrapper(**proxy_params)
+
+ return kwargs
+
+ def _update_costs(self, usage: Union[dict, BaseModel], model: str = None, local_calc_usage: bool = True):
+ if next(iter(DOUBAO_TOKEN_COSTS)) not in self.cost_manager.token_costs:
+ self.cost_manager.token_costs.update(DOUBAO_TOKEN_COSTS)
+ if model in self.cost_manager.token_costs:
+ self.pricing_plan = model
+ if self.pricing_plan in self.cost_manager.token_costs:
+ super()._update_costs(usage, self.pricing_plan, local_calc_usage)
+
+ async def _achat_completion_stream(self, messages: list[dict], timeout=USE_CONFIG_TIMEOUT) -> str:
+ response: AsyncStream[ChatCompletionChunk] = await self.aclient.chat.completions.create(
+ **self._cons_kwargs(messages, timeout=self.get_timeout(timeout)),
+ stream=True,
+ extra_body={"stream_options": {"include_usage": True}}, # 只有增加这个参数才会在流式时最后返回usage
+ )
+ usage = None
+ collected_messages = []
+ async for chunk in response:
+ chunk_message = chunk.choices[0].delta.content or "" if chunk.choices else "" # extract the message
+ log_llm_stream(chunk_message)
+ collected_messages.append(chunk_message)
+ if chunk.usage:
+ # 火山方舟的流式调用会在最后一个chunk中返回usage,最后一个chunk的choices为[]
+ usage = chunk.usage
+
+ log_llm_stream("\n")
+ full_reply_content = "".join(collected_messages)
+ self._update_costs(usage, chunk.model)
+ return full_reply_content
+
+ async def _achat_completion(self, messages: list[dict], timeout=USE_CONFIG_TIMEOUT) -> ChatCompletion:
+ kwargs = self._cons_kwargs(messages, timeout=self.get_timeout(timeout))
+ rsp: ChatCompletion = await self.aclient.chat.completions.create(**kwargs)
+ self._update_costs(rsp.usage, rsp.model)
+ return rsp
diff --git a/metagpt/provider/base_llm.py b/metagpt/provider/base_llm.py
index f9111ffe0..ec9e7fcf4 100644
--- a/metagpt/provider/base_llm.py
+++ b/metagpt/provider/base_llm.py
@@ -119,7 +119,7 @@ class BaseLLM(ABC):
model = model or self.pricing_plan
model = model or self.model
usage = usage.model_dump() if isinstance(usage, BaseModel) else usage
- if calc_usage and self.cost_manager:
+ if calc_usage and self.cost_manager and usage:
try:
prompt_tokens = int(usage.get("prompt_tokens", 0))
completion_tokens = int(usage.get("completion_tokens", 0))
@@ -139,7 +139,7 @@ class BaseLLM(ABC):
format_msgs: Optional[list[dict[str, str]]] = None,
images: Optional[Union[str, list[str]]] = None,
timeout=USE_CONFIG_TIMEOUT,
- stream=True,
+ stream=None,
) -> str:
if system_msgs:
message = self._system_msgs(system_msgs)
@@ -153,6 +153,8 @@ class BaseLLM(ABC):
message.append(self._user_msg(msg, images=images))
else:
message.extend(msg)
+ if stream is None:
+ stream = self.config.stream
logger.debug(message)
compressed_message = self.compress_messages(message, compress_type=self.config.compress_type)
rsp = await self.acompletion_text(compressed_message, stream=stream, timeout=self.get_timeout(timeout))
diff --git a/metagpt/provider/bedrock/__init__.py b/metagpt/provider/bedrock/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/metagpt/provider/bedrock/base_provider.py b/metagpt/provider/bedrock/base_provider.py
new file mode 100644
index 000000000..ebc55483b
--- /dev/null
+++ b/metagpt/provider/bedrock/base_provider.py
@@ -0,0 +1,28 @@
+import json
+from abc import ABC, abstractmethod
+
+
+class BaseBedrockProvider(ABC):
+ # to handle different generation kwargs
+ max_tokens_field_name = "max_tokens"
+
+ @abstractmethod
+ def _get_completion_from_dict(self, rsp_dict: dict) -> str:
+ ...
+
+ def get_request_body(self, messages: list[dict], const_kwargs, *args, **kwargs) -> str:
+ body = json.dumps({"prompt": self.messages_to_prompt(messages), **const_kwargs})
+ return body
+
+ def get_choice_text(self, response_body: dict) -> str:
+ completions = self._get_completion_from_dict(response_body)
+ return completions
+
+ def get_choice_text_from_stream(self, event) -> str:
+ rsp_dict = json.loads(event["chunk"]["bytes"])
+ completions = self._get_completion_from_dict(rsp_dict)
+ return completions
+
+ def messages_to_prompt(self, messages: list[dict]) -> str:
+ """[{"role": "user", "content": msg}] to user: etc."""
+ return "\n".join([f"{msg['role']}: {msg['content']}" for msg in messages])
diff --git a/metagpt/provider/bedrock/bedrock_provider.py b/metagpt/provider/bedrock/bedrock_provider.py
new file mode 100644
index 000000000..c5e3b7bd2
--- /dev/null
+++ b/metagpt/provider/bedrock/bedrock_provider.py
@@ -0,0 +1,191 @@
+import json
+from typing import Literal, Tuple
+
+from metagpt.provider.bedrock.base_provider import BaseBedrockProvider
+from metagpt.provider.bedrock.utils import (
+ messages_to_prompt_llama2,
+ messages_to_prompt_llama3,
+)
+
+
+class MistralProvider(BaseBedrockProvider):
+ # See https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-mistral.html
+
+ def messages_to_prompt(self, messages: list[dict]):
+ return messages_to_prompt_llama2(messages)
+
+ def _get_completion_from_dict(self, rsp_dict: dict) -> str:
+ return rsp_dict["outputs"][0]["text"]
+
+
+class AnthropicProvider(BaseBedrockProvider):
+ # See https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-anthropic-claude-messages.html
+
+ def _split_system_user_messages(self, messages: list[dict]) -> Tuple[str, list[dict]]:
+ system_messages = []
+ user_messages = []
+ for message in messages:
+ if message["role"] == "system":
+ system_messages.append(message)
+ else:
+ user_messages.append(message)
+ return self.messages_to_prompt(system_messages), user_messages
+
+ def get_request_body(self, messages: list[dict], generate_kwargs, *args, **kwargs) -> str:
+ system_message, user_messages = self._split_system_user_messages(messages)
+ body = json.dumps(
+ {
+ "messages": user_messages,
+ "anthropic_version": "bedrock-2023-05-31",
+ "system": system_message,
+ **generate_kwargs,
+ }
+ )
+ return body
+
+ def _get_completion_from_dict(self, rsp_dict: dict) -> str:
+ return rsp_dict["content"][0]["text"]
+
+ def get_choice_text_from_stream(self, event) -> str:
+ # https://docs.anthropic.com/claude/reference/messages-streaming
+ rsp_dict = json.loads(event["chunk"]["bytes"])
+ if rsp_dict["type"] == "content_block_delta":
+ completions = rsp_dict["delta"]["text"]
+ return completions
+ else:
+ return ""
+
+
+class CohereProvider(BaseBedrockProvider):
+ # For more information, see
+ # (Command) https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-cohere-command.html
+ # (Command R/R+) https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-cohere-command-r-plus.html
+
+ def __init__(self, model_name: str) -> None:
+ self.model_name = model_name
+
+ def _get_completion_from_dict(self, rsp_dict: dict) -> str:
+ return rsp_dict["generations"][0]["text"]
+
+ def messages_to_prompt(self, messages: list[dict]) -> str:
+ if "command-r" in self.model_name:
+ role_map = {"user": "USER", "assistant": "CHATBOT", "system": "USER"}
+ messages = list(
+ map(lambda message: {"role": role_map[message["role"]], "message": message["content"]}, messages)
+ )
+ return messages
+ else:
+ """[{"role": "user", "content": msg}] to user: etc."""
+ return "\n".join([f"{msg['role']}: {msg['content']}" for msg in messages])
+
+ def get_request_body(self, messages: list[dict], generate_kwargs, *args, **kwargs):
+ prompt = self.messages_to_prompt(messages)
+ if "command-r" in self.model_name:
+ chat_history, message = prompt[:-1], prompt[-1]["message"]
+ body = json.dumps({"message": message, "chat_history": chat_history, **generate_kwargs})
+ else:
+ body = json.dumps({"prompt": prompt, "stream": kwargs.get("stream", False), **generate_kwargs})
+ return body
+
+ def get_choice_text_from_stream(self, event) -> str:
+ rsp_dict = json.loads(event["chunk"]["bytes"])
+ completions = rsp_dict.get("text", "")
+ return completions
+
+
+class MetaProvider(BaseBedrockProvider):
+ # See https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-meta.html
+
+ max_tokens_field_name = "max_gen_len"
+
+ def __init__(self, llama_version: Literal["llama2", "llama3"]) -> None:
+ self.llama_version = llama_version
+
+ def messages_to_prompt(self, messages: list[dict]):
+ if self.llama_version == "llama2":
+ return messages_to_prompt_llama2(messages)
+ else:
+ return messages_to_prompt_llama3(messages)
+
+ def _get_completion_from_dict(self, rsp_dict: dict) -> str:
+ return rsp_dict["generation"]
+
+
+class Ai21Provider(BaseBedrockProvider):
+ # See https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-jurassic2.html
+
+ def __init__(self, model_type: Literal["j2", "jamba"]) -> None:
+ self.model_type = model_type
+ if self.model_type == "j2":
+ self.max_tokens_field_name = "maxTokens"
+ else:
+ self.max_tokens_field_name = "max_tokens"
+
+ def get_request_body(self, messages: list[dict], generate_kwargs, *args, **kwargs) -> str:
+ if self.model_type == "j2":
+ body = super().get_request_body(messages, generate_kwargs, *args, **kwargs)
+ else:
+ body = json.dumps(
+ {
+ "messages": messages,
+ **generate_kwargs,
+ }
+ )
+ return body
+
+ def get_choice_text_from_stream(self, event) -> str:
+ rsp_dict = json.loads(event["chunk"]["bytes"])
+ completions = rsp_dict.get("choices", [{}])[0].get("delta", {}).get("content", "")
+ return completions
+
+ def _get_completion_from_dict(self, rsp_dict: dict) -> str:
+ if self.model_type == "j2":
+ # See https://docs.ai21.com/reference/j2-complete-ref
+ return rsp_dict["completions"][0]["data"]["text"]
+ else:
+ # See https://docs.ai21.com/reference/jamba-instruct-api
+ return rsp_dict["choices"][0]["message"]["content"]
+
+
+class AmazonProvider(BaseBedrockProvider):
+ # See https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-titan-text.html
+
+ max_tokens_field_name = "maxTokenCount"
+
+ def get_request_body(self, messages: list[dict], generate_kwargs, *args, **kwargs):
+ body = json.dumps({"inputText": self.messages_to_prompt(messages), "textGenerationConfig": generate_kwargs})
+ return body
+
+ def _get_completion_from_dict(self, rsp_dict: dict) -> str:
+ return rsp_dict["results"][0]["outputText"]
+
+ def get_choice_text_from_stream(self, event) -> str:
+ rsp_dict = json.loads(event["chunk"]["bytes"])
+ completions = rsp_dict["outputText"]
+ return completions
+
+
+PROVIDERS = {
+ "mistral": MistralProvider,
+ "meta": MetaProvider,
+ "ai21": Ai21Provider,
+ "cohere": CohereProvider,
+ "anthropic": AnthropicProvider,
+ "amazon": AmazonProvider,
+}
+
+
+def get_provider(model_id: str):
+ provider, model_name = model_id.split(".")[0:2] # meta、mistral……
+ if provider not in PROVIDERS:
+ raise KeyError(f"{provider} is not supported!")
+ if provider == "meta":
+ # distinguish llama2 and llama3
+ return PROVIDERS[provider](model_name[:6])
+ elif provider == "ai21":
+ # distinguish between j2 and jamba
+ return PROVIDERS[provider](model_name.split("-")[0])
+ elif provider == "cohere":
+ # distinguish between R/R+ and older models
+ return PROVIDERS[provider](model_name)
+ return PROVIDERS[provider]()
diff --git a/metagpt/provider/bedrock/utils.py b/metagpt/provider/bedrock/utils.py
new file mode 100644
index 000000000..e67796362
--- /dev/null
+++ b/metagpt/provider/bedrock/utils.py
@@ -0,0 +1,158 @@
+from metagpt.logs import logger
+
+# max_tokens for each model
+NOT_SUPPORT_STREAM_MODELS = {
+ # Jurassic-2 Mid-v1 and Ultra-v1
+ # + Legacy date: 2024-04-30 (us-west-2/Oregon)
+ # + EOL date: 2024-08-31 (us-west-2/Oregon)
+ "ai21.j2-mid-v1": 8191,
+ "ai21.j2-ultra-v1": 8191,
+}
+
+SUPPORT_STREAM_MODELS = {
+ # Jamba-Instruct
+ # https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-jamba.html
+ "ai21.jamba-instruct-v1:0": 4096,
+ # Titan Text G1 - Lite
+ # https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-titan-text.html
+ "amazon.titan-text-lite-v1:0:4k": 4096,
+ "amazon.titan-text-lite-v1": 4096,
+ # Titan Text G1 - Express
+ "amazon.titan-text-express-v1": 8192,
+ "amazon.titan-text-express-v1:0:8k": 8192,
+ # Titan Text Premier
+ "amazon.titan-text-premier-v1:0": 3072,
+ "amazon.titan-text-premier-v1:0:32k": 3072,
+ # Claude Instant v1
+ # https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-anthropic-claude-text-completion.html
+ # https://docs.anthropic.com/en/docs/about-claude/models#model-comparison
+ "anthropic.claude-instant-v1": 4096,
+ "anthropic.claude-instant-v1:2:100k": 4096,
+ # Claude v2
+ "anthropic.claude-v2": 4096,
+ "anthropic.claude-v2:0:18k": 4096,
+ "anthropic.claude-v2:0:100k": 4096,
+ # Claude v2.1
+ "anthropic.claude-v2:1": 4096,
+ "anthropic.claude-v2:1:18k": 4096,
+ "anthropic.claude-v2:1:200k": 4096,
+ # Claude 3 Sonnet
+ "anthropic.claude-3-sonnet-20240229-v1:0": 4096,
+ "anthropic.claude-3-sonnet-20240229-v1:0:28k": 4096,
+ "anthropic.claude-3-sonnet-20240229-v1:0:200k": 4096,
+ # Claude 3 Haiku
+ "anthropic.claude-3-haiku-20240307-v1:0": 4096,
+ "anthropic.claude-3-haiku-20240307-v1:0:48k": 4096,
+ "anthropic.claude-3-haiku-20240307-v1:0:200k": 4096,
+ # Claude 3 Opus
+ "anthropic.claude-3-opus-20240229-v1:0": 4096,
+ # Claude 3.5 Sonnet
+ "anthropic.claude-3-5-sonnet-20240620-v1:0": 8192,
+ # Command Text
+ # https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-cohere-command.html
+ "cohere.command-text-v14": 4096,
+ "cohere.command-text-v14:7:4k": 4096,
+ # Command Light Text
+ "cohere.command-light-text-v14": 4096,
+ "cohere.command-light-text-v14:7:4k": 4096,
+ # Command R
+ # https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-cohere-command-r-plus.html
+ "cohere.command-r-v1:0": 4096,
+ # Command R+
+ "cohere.command-r-plus-v1:0": 4096,
+ # Llama 2 (--> Llama 3/3.1/3.2) !!!
+ # https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-meta.html
+ # + Legacy: 2024-05-12
+ # + EOL: 2024-10-30
+ # "meta.llama2-13b-chat-v1": 2048,
+ # "meta.llama2-13b-chat-v1:0:4k": 2048,
+ # "meta.llama2-70b-v1": 2048,
+ # "meta.llama2-70b-v1:0:4k": 2048,
+ # "meta.llama2-70b-chat-v1": 2048,
+ # "meta.llama2-70b-chat-v1:0:4k": 2048,
+ # Llama 3 Instruct
+ # "meta.llama3-8b-instruct-v1:0": 2048,
+ "meta.llama3-70b-instruct-v1:0": 2048,
+ # Llama 3.1 Instruct
+ # "meta.llama3-1-8b-instruct-v1:0": 2048,
+ "meta.llama3-1-70b-instruct-v1:0": 2048,
+ "meta.llama3-1-405b-instruct-v1:0": 2048,
+ # Llama 3.2 Instruct
+ # "meta.llama3-2-3b-instruct-v1:0": 2048,
+ # "meta.llama3-2-11b-instruct-v1:0": 2048,
+ "meta.llama3-2-90b-instruct-v1:0": 2048,
+ # Mistral 7B Instruct
+ # https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-mistral-text-completion.html
+ # "mistral.mistral-7b-instruct-v0:2": 8192,
+ # Mixtral 8x7B Instruct
+ "mistral.mixtral-8x7b-instruct-v0:1": 4096,
+ # Mistral Small
+ "mistral.mistral-small-2402-v1:0": 8192,
+ # Mistral Large (24.02)
+ "mistral.mistral-large-2402-v1:0": 8192,
+ # Mistral Large 2 (24.07)
+ "mistral.mistral-large-2407-v1:0": 8192,
+}
+
+# TODO:use a more general function for constructing chat templates.
+
+
+def messages_to_prompt_llama2(messages: list[dict]) -> str:
+ BOS = ("",)
+ B_INST, E_INST = "[INST]", "[/INST]"
+ B_SYS, E_SYS = "<>\n", "\n<>\n\n"
+
+ prompt = f"{BOS}"
+ for message in messages:
+ role = message.get("role", "")
+ content = message.get("content", "")
+ if role == "system":
+ prompt += f"{B_SYS} {content} {E_SYS}"
+ elif role == "user":
+ prompt += f"{B_INST} {content} {E_INST}"
+ elif role == "assistant":
+ prompt += f"{content}"
+ else:
+ logger.warning(f"Unknown role name {role} when formatting messages")
+ prompt += f"{content}"
+
+ return prompt
+
+
+def messages_to_prompt_llama3(messages: list[dict]) -> str:
+ BOS = "<|begin_of_text|>"
+ GENERAL_TEMPLATE = "<|start_header_id|>{role}<|end_header_id|>\n\n{content}<|eot_id|>"
+
+ prompt = f"{BOS}"
+ for message in messages:
+ role = message.get("role", "")
+ content = message.get("content", "")
+ prompt += GENERAL_TEMPLATE.format(role=role, content=content)
+
+ if role != "assistant":
+ prompt += "<|start_header_id|>assistant<|end_header_id|>"
+
+ return prompt
+
+
+def messages_to_prompt_claude2(messages: list[dict]) -> str:
+ GENERAL_TEMPLATE = "\n\n{role}: {content}"
+ prompt = ""
+ for message in messages:
+ role = message.get("role", "")
+ content = message.get("content", "")
+ prompt += GENERAL_TEMPLATE.format(role=role, content=content)
+
+ if role != "assistant":
+ prompt += "\n\nAssistant:"
+
+ return prompt
+
+
+def get_max_tokens(model_id: str) -> int:
+ try:
+ max_tokens = (NOT_SUPPORT_STREAM_MODELS | SUPPORT_STREAM_MODELS)[model_id]
+ except KeyError:
+ logger.warning(f"Couldn't find model:{model_id} , max tokens has been set to 2048")
+ max_tokens = 2048
+ return max_tokens
diff --git a/metagpt/provider/bedrock_api.py b/metagpt/provider/bedrock_api.py
new file mode 100644
index 000000000..72eefa7e5
--- /dev/null
+++ b/metagpt/provider/bedrock_api.py
@@ -0,0 +1,153 @@
+import asyncio
+import json
+import os
+from functools import partial
+from typing import List, Literal
+
+import boto3
+from botocore.eventstream import EventStream
+
+from metagpt.configs.llm_config import LLMConfig, LLMType
+from metagpt.const import USE_CONFIG_TIMEOUT
+from metagpt.logs import log_llm_stream, logger
+from metagpt.provider.base_llm import BaseLLM
+from metagpt.provider.bedrock.bedrock_provider import get_provider
+from metagpt.provider.bedrock.utils import NOT_SUPPORT_STREAM_MODELS, get_max_tokens
+from metagpt.provider.llm_provider_registry import register_provider
+from metagpt.utils.cost_manager import CostManager
+from metagpt.utils.token_counter import BEDROCK_TOKEN_COSTS
+
+
+@register_provider([LLMType.BEDROCK])
+class BedrockLLM(BaseLLM):
+ def __init__(self, config: LLMConfig):
+ self.config = config
+ self.__client = self.__init_client("bedrock-runtime")
+ self.__provider = get_provider(self.config.model)
+ self.cost_manager = CostManager(token_costs=BEDROCK_TOKEN_COSTS)
+ if self.config.model in NOT_SUPPORT_STREAM_MODELS:
+ logger.warning(f"model {self.config.model} doesn't support streaming output!")
+
+ def __init_client(self, service_name: Literal["bedrock-runtime", "bedrock"]):
+ """initialize boto3 client"""
+ # access key and secret key from https://us-east-1.console.aws.amazon.com/iam
+ self.__credential_kwargs = {
+ "aws_secret_access_key": os.environ.get("AWS_SECRET_ACCESS_KEY", self.config.secret_key),
+ "aws_access_key_id": os.environ.get("AWS_ACCESS_KEY_ID", self.config.access_key),
+ "aws_session_token": os.environ.get("AWS_SESSION_TOKEN", self.config.session_token),
+ "region_name": os.environ.get("AWS_DEFAULT_REGION", self.config.region_name),
+ }
+ session = boto3.Session(**self.__credential_kwargs)
+ client = session.client(service_name, region_name=self.__credential_kwargs["region_name"])
+ return client
+
+ @property
+ def client(self):
+ return self.__client
+
+ @property
+ def provider(self):
+ return self.__provider
+
+ def list_models(self):
+ """list all available text-generation models
+
+ ```shell
+ ai21.j2-ultra-v1 Support Streaming:False
+ meta.llama3-70b-instruct-v1:0 Support Streaming:True
+ ……
+ ```
+ """
+ client = self.__init_client("bedrock")
+ # only output text-generation models
+ response = client.list_foundation_models(byOutputModality="TEXT")
+ summaries = [
+ f'{summary["modelId"]:50} Support Streaming:{summary["responseStreamingSupported"]}'
+ for summary in response["modelSummaries"]
+ ]
+ logger.info("\n" + "\n".join(summaries))
+
+ async def invoke_model(self, request_body: str) -> dict:
+ loop = asyncio.get_running_loop()
+ response = await loop.run_in_executor(
+ None, partial(self.client.invoke_model, modelId=self.config.model, body=request_body)
+ )
+ usage = self._get_usage(response)
+ self._update_costs(usage, self.config.model)
+ response_body = self._get_response_body(response)
+ return response_body
+
+ async def invoke_model_with_response_stream(self, request_body: str) -> EventStream:
+ loop = asyncio.get_running_loop()
+ response = await loop.run_in_executor(
+ None, partial(self.client.invoke_model_with_response_stream, modelId=self.config.model, body=request_body)
+ )
+ usage = self._get_usage(response)
+ self._update_costs(usage, self.config.model)
+ return response
+
+ @property
+ def _const_kwargs(self) -> dict:
+ model_max_tokens = get_max_tokens(self.config.model)
+ if self.config.max_token > model_max_tokens:
+ max_tokens = model_max_tokens
+ else:
+ max_tokens = self.config.max_token
+
+ return {self.__provider.max_tokens_field_name: max_tokens, "temperature": self.config.temperature}
+
+ # boto3 don't support support asynchronous calls.
+ # for asynchronous version of boto3, check out:
+ # https://aioboto3.readthedocs.io/en/latest/usage.html
+ # However,aioboto3 doesn't support invoke model
+
+ def get_choice_text(self, rsp: dict) -> str:
+ return self.__provider.get_choice_text(rsp)
+
+ async def acompletion(self, messages: list[dict]) -> dict:
+ request_body = self.__provider.get_request_body(messages, self._const_kwargs)
+ response_body = await self.invoke_model(request_body)
+ return response_body
+
+ async def _achat_completion(self, messages: list[dict], timeout=USE_CONFIG_TIMEOUT) -> dict:
+ return await self.acompletion(messages)
+
+ async def _achat_completion_stream(self, messages: list[dict], timeout=USE_CONFIG_TIMEOUT) -> str:
+ if self.config.model in NOT_SUPPORT_STREAM_MODELS:
+ rsp = await self.acompletion(messages)
+ full_text = self.get_choice_text(rsp)
+ log_llm_stream(full_text)
+ return full_text
+
+ request_body = self.__provider.get_request_body(messages, self._const_kwargs, stream=True)
+ stream_response = await self.invoke_model_with_response_stream(request_body)
+ collected_content = await self._get_stream_response_body(stream_response)
+ log_llm_stream("\n")
+ full_text = ("".join(collected_content)).lstrip()
+ return full_text
+
+ def _get_response_body(self, response) -> dict:
+ response_body = json.loads(response["body"].read())
+ return response_body
+
+ async def _get_stream_response_body(self, stream_response) -> List[str]:
+ def collect_content() -> str:
+ collected_content = []
+ for event in stream_response["body"]:
+ chunk_text = self.__provider.get_choice_text_from_stream(event)
+ collected_content.append(chunk_text)
+ log_llm_stream(chunk_text)
+ return collected_content
+
+ loop = asyncio.get_running_loop()
+ return await loop.run_in_executor(None, collect_content)
+
+ def _get_usage(self, response) -> dict[str, int]:
+ headers = response.get("ResponseMetadata", {}).get("HTTPHeaders", {})
+ prompt_tokens = int(headers.get("x-amzn-bedrock-input-token-count", 0))
+ completion_tokens = int(headers.get("x-amzn-bedrock-output-token-count", 0))
+ usage = {
+ "prompt_tokens": prompt_tokens,
+ "completion_tokens": completion_tokens,
+ }
+ return usage
diff --git a/metagpt/provider/dashscope_api.py b/metagpt/provider/dashscope_api.py
index 82224e893..837377edc 100644
--- a/metagpt/provider/dashscope_api.py
+++ b/metagpt/provider/dashscope_api.py
@@ -48,13 +48,17 @@ def build_api_arequest(
request_timeout,
form,
resources,
+ base_address,
+ _,
) = _get_protocol_params(kwargs)
task_id = kwargs.pop("task_id", None)
if api_protocol in [ApiProtocol.HTTP, ApiProtocol.HTTPS]:
- if not dashscope.base_http_api_url.endswith("/"):
- http_url = dashscope.base_http_api_url + "/"
+ if base_address is None:
+ base_address = dashscope.base_http_api_url
+ if not base_address.endswith("/"):
+ http_url = base_address + "/"
else:
- http_url = dashscope.base_http_api_url
+ http_url = base_address
if is_service:
http_url = http_url + SERVICE_API_PATH + "/"
diff --git a/metagpt/provider/general_api_base.py b/metagpt/provider/general_api_base.py
index 8e5da8f16..34a39fe6c 100644
--- a/metagpt/provider/general_api_base.py
+++ b/metagpt/provider/general_api_base.py
@@ -13,6 +13,7 @@ import time
from contextlib import asynccontextmanager
from enum import Enum
from typing import (
+ Any,
AsyncGenerator,
AsyncIterator,
Dict,
@@ -121,7 +122,7 @@ def logfmt(props):
class OpenAIResponse:
- def __init__(self, data, headers):
+ def __init__(self, data: Union[bytes, Any], headers: dict):
self._headers = headers
self.data = data
@@ -320,49 +321,6 @@ class APIRequestor:
resp, got_stream = self._interpret_response(result, stream)
return resp, got_stream, self.api_key
- @overload
- async def arequest(
- self,
- method,
- url,
- params,
- headers,
- files,
- stream: Literal[True],
- request_id: Optional[str] = ...,
- request_timeout: Optional[Union[float, Tuple[float, float]]] = ...,
- ) -> Tuple[AsyncGenerator[OpenAIResponse, None], bool, str]:
- pass
-
- @overload
- async def arequest(
- self,
- method,
- url,
- params=...,
- headers=...,
- files=...,
- *,
- stream: Literal[True],
- request_id: Optional[str] = ...,
- request_timeout: Optional[Union[float, Tuple[float, float]]] = ...,
- ) -> Tuple[AsyncGenerator[OpenAIResponse, None], bool, str]:
- pass
-
- @overload
- async def arequest(
- self,
- method,
- url,
- params=...,
- headers=...,
- files=...,
- stream: Literal[False] = ...,
- request_id: Optional[str] = ...,
- request_timeout: Optional[Union[float, Tuple[float, float]]] = ...,
- ) -> Tuple[OpenAIResponse, bool, str]:
- pass
-
@overload
async def arequest(
self,
@@ -438,8 +396,8 @@ class APIRequestor:
"X-LLM-Client-User-Agent": json.dumps(ua),
"User-Agent": user_agent,
}
-
- headers.update(api_key_to_header(self.api_type, self.api_key))
+ if self.api_key:
+ headers.update(api_key_to_header(self.api_type, self.api_key))
if self.organization:
headers["LLM-Organization"] = self.organization
diff --git a/metagpt/provider/general_api_requestor.py b/metagpt/provider/general_api_requestor.py
index 18f4dd909..b8da1565d 100644
--- a/metagpt/provider/general_api_requestor.py
+++ b/metagpt/provider/general_api_requestor.py
@@ -3,25 +3,24 @@
# @Desc : General Async API for http-based LLM model
import asyncio
-from typing import AsyncGenerator, Generator, Iterator, Tuple, Union
+from typing import AsyncGenerator, Iterator, Optional, Tuple, Union
import aiohttp
import requests
from metagpt.logs import logger
-from metagpt.provider.general_api_base import APIRequestor
+from metagpt.provider.general_api_base import APIRequestor, OpenAIResponse
-def parse_stream_helper(line: bytes) -> Union[bytes, None]:
+def parse_stream_helper(line: bytes) -> Optional[bytes]:
if line and line.startswith(b"data:"):
if line.startswith(b"data: "):
- # SSE event may be valid when it contain whitespace
+ # SSE event may be valid when it contains whitespace
line = line[len(b"data: ") :]
else:
line = line[len(b"data:") :]
if line.strip() == b"[DONE]":
- # return here will cause GeneratorExit exception in urllib3
- # and it will close http connection with TCP Reset
+ # Returning None to indicate end of stream
return None
else:
return line
@@ -37,7 +36,7 @@ def parse_stream(rbody: Iterator[bytes]) -> Iterator[bytes]:
class GeneralAPIRequestor(APIRequestor):
"""
- usage
+ Usage example:
# full_url = "{base_url}{url}"
requester = GeneralAPIRequestor(base_url=base_url)
result, _, api_key = await requester.arequest(
@@ -50,26 +49,47 @@ class GeneralAPIRequestor(APIRequestor):
)
"""
- def _interpret_response_line(self, rbody: bytes, rcode: int, rheaders, stream: bool) -> bytes:
- # just do nothing to meet the APIRequestor process and return the raw data
- # due to the openai sdk will convert the data into OpenAIResponse which we don't need in general cases.
+ def _interpret_response_line(self, rbody: bytes, rcode: int, rheaders: dict, stream: bool) -> OpenAIResponse:
+ """
+ Process and return the response data wrapped in OpenAIResponse.
- return rbody
+ Args:
+ rbody (bytes): The response body.
+ rcode (int): The response status code.
+ rheaders (dict): The response headers.
+ stream (bool): Whether the response is a stream.
+
+ Returns:
+ OpenAIResponse: The response data wrapped in OpenAIResponse.
+ """
+ return OpenAIResponse(rbody, rheaders)
def _interpret_response(
self, result: requests.Response, stream: bool
- ) -> Tuple[Union[bytes, Iterator[Generator]], bytes]:
- """Returns the response(s) and a bool indicating whether it is a stream."""
+ ) -> Tuple[Union[OpenAIResponse, Iterator[OpenAIResponse]], bool]:
+ """
+ Interpret a synchronous response.
+
+ Args:
+ result (requests.Response): The response object.
+ stream (bool): Whether the response is a stream.
+
+ Returns:
+ Tuple[Union[OpenAIResponse, Iterator[OpenAIResponse]], bool]: A tuple containing the response content and a boolean indicating if it is a stream.
+ """
content_type = result.headers.get("Content-Type", "")
if stream and ("text/event-stream" in content_type or "application/x-ndjson" in content_type):
return (
- self._interpret_response_line(line, result.status_code, result.headers, stream=True)
- for line in parse_stream(result.iter_lines())
- ), True
+ (
+ self._interpret_response_line(line, result.status_code, result.headers, stream=True)
+ for line in parse_stream(result.iter_lines())
+ ),
+ True,
+ )
else:
return (
self._interpret_response_line(
- result.content, # let the caller to decode the msg
+ result.content, # let the caller decode the msg
result.status_code,
result.headers,
stream=False,
@@ -79,24 +99,39 @@ class GeneralAPIRequestor(APIRequestor):
async def _interpret_async_response(
self, result: aiohttp.ClientResponse, stream: bool
- ) -> Tuple[Union[bytes, AsyncGenerator[bytes, None]], bool]:
+ ) -> Tuple[Union[OpenAIResponse, AsyncGenerator[OpenAIResponse, None]], bool]:
+ """
+ Interpret an asynchronous response.
+
+ Args:
+ result (aiohttp.ClientResponse): The response object.
+ stream (bool): Whether the response is a stream.
+
+ Returns:
+ Tuple[Union[OpenAIResponse, AsyncGenerator[OpenAIResponse, None]], bool]: A tuple containing the response content and a boolean indicating if it is a stream.
+ """
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"
+ if stream and (
+ "text/event-stream" in content_type or "application/x-ndjson" in content_type or content_type == ""
+ ):
return (
- self._interpret_response_line(line, result.status, result.headers, stream=True)
- async for line in result.content
- ), True
+ (
+ self._interpret_response_line(line, result.status, result.headers, stream=True)
+ async for line in result.content
+ ),
+ True,
+ )
else:
try:
- await result.read()
+ response_content = await result.read()
except (aiohttp.ServerTimeoutError, asyncio.TimeoutError) as e:
raise TimeoutError("Request timed out") from e
except aiohttp.ClientError as exp:
- logger.warning(f"response: {result.content}, exp: {exp}")
+ logger.warning(f"response: {result}, exp: {exp}")
+ response_content = b""
return (
self._interpret_response_line(
- await result.read(), # let the caller to decode the msg
+ response_content, # let the caller decode the msg
result.status,
result.headers,
stream=False,
diff --git a/metagpt/provider/human_provider.py b/metagpt/provider/human_provider.py
index 87dbd105f..a16a49c20 100644
--- a/metagpt/provider/human_provider.py
+++ b/metagpt/provider/human_provider.py
@@ -33,6 +33,7 @@ class HumanProvider(BaseLLM):
format_msgs: Optional[list[dict[str, str]]] = None,
generator: bool = False,
timeout=USE_CONFIG_TIMEOUT,
+ **kwargs
) -> str:
return self.ask(msg, timeout=self.get_timeout(timeout))
diff --git a/metagpt/provider/llm_provider_registry.py b/metagpt/provider/llm_provider_registry.py
index 4fd2b1978..7f8618590 100644
--- a/metagpt/provider/llm_provider_registry.py
+++ b/metagpt/provider/llm_provider_registry.py
@@ -37,7 +37,11 @@ def register_provider(keys):
def create_llm_instance(config: LLMConfig) -> BaseLLM:
"""get the default llm provider"""
- return LLM_REGISTRY.get_provider(config.api_type)(config)
+ llm = LLM_REGISTRY.get_provider(config.api_type)(config)
+ if llm.use_system_prompt and not config.use_system_prompt:
+ # for models like o1-series, default openai provider.use_system_prompt is True, but it should be False for o1-*
+ llm.use_system_prompt = config.use_system_prompt
+ return llm
# Registry instance
diff --git a/metagpt/provider/ollama_api.py b/metagpt/provider/ollama_api.py
index 2913eb1dd..3f7d20d0a 100644
--- a/metagpt/provider/ollama_api.py
+++ b/metagpt/provider/ollama_api.py
@@ -3,16 +3,189 @@
# @Desc : self-host open llm model with ollama which isn't openai-api-compatible
import json
+from enum import Enum, auto
+from typing import AsyncGenerator, Optional, Tuple
from metagpt.configs.llm_config import LLMConfig, LLMType
from metagpt.const import USE_CONFIG_TIMEOUT
from metagpt.logs import log_llm_stream
from metagpt.provider.base_llm import BaseLLM
-from metagpt.provider.general_api_requestor import GeneralAPIRequestor
+from metagpt.provider.general_api_requestor import GeneralAPIRequestor, OpenAIResponse
from metagpt.provider.llm_provider_registry import register_provider
from metagpt.utils.cost_manager import TokenCostManager
+class OllamaMessageAPI(Enum):
+ # default
+ CHAT = auto()
+ GENERATE = auto()
+ EMBED = auto()
+ EMBEDDINGS = auto()
+
+
+class OllamaMessageBase:
+ api_type = OllamaMessageAPI.CHAT
+
+ def __init__(self, model: str, **additional_kwargs) -> None:
+ self.model, self.additional_kwargs = model, additional_kwargs
+ self._image_b64_rms = len("data:image/jpeg;base64,")
+
+ @property
+ def api_suffix(self) -> str:
+ raise NotImplementedError
+
+ def apply(self, messages: list[dict]) -> dict:
+ raise NotImplementedError
+
+ def decode(self, response: OpenAIResponse) -> dict:
+ return json.loads(response.data.decode("utf-8"))
+
+ def get_choice(self, to_choice_dict: dict) -> str:
+ raise NotImplementedError
+
+ def _parse_input_msg(self, msg: dict) -> Tuple[Optional[str], Optional[str]]:
+ if "type" in msg:
+ tpe = msg["type"]
+ if tpe == "text":
+ return msg["text"], None
+ elif tpe == "image_url":
+ return None, msg["image_url"]["url"][self._image_b64_rms :]
+ else:
+ raise ValueError
+ else:
+ raise ValueError
+
+
+class OllamaMessageMeta(type):
+ registed_message = {}
+
+ def __init__(cls, name, bases, attrs):
+ super().__init__(name, bases, attrs)
+ for base in bases:
+ if issubclass(base, OllamaMessageBase):
+ api_type = attrs["api_type"]
+ assert api_type not in OllamaMessageMeta.registed_message, "api_type already exist"
+ assert isinstance(api_type, OllamaMessageAPI), "api_type not support"
+ OllamaMessageMeta.registed_message[api_type] = cls
+
+ @classmethod
+ def get_message(cls, input_type: OllamaMessageAPI) -> type[OllamaMessageBase]:
+ return cls.registed_message[input_type]
+
+
+class OllamaMessageChat(OllamaMessageBase, metaclass=OllamaMessageMeta):
+ api_type = OllamaMessageAPI.CHAT
+
+ @property
+ def api_suffix(self) -> str:
+ return "/chat"
+
+ def apply(self, messages: list[dict]) -> dict:
+ content = messages[0]["content"]
+ prompts = []
+ images = []
+ if isinstance(content, list):
+ for msg in content:
+ prompt, image = self._parse_input_msg(msg)
+ if prompt:
+ prompts.append(prompt)
+ if image:
+ images.append(image)
+ else:
+ prompts.append(content)
+ messes = []
+ for prompt in prompts:
+ if len(images) > 0:
+ messes.append({"role": "user", "content": prompt, "images": images})
+ else:
+ messes.append({"role": "user", "content": prompt})
+ sends = {"model": self.model, "messages": messes}
+ sends.update(self.additional_kwargs)
+ return sends
+
+ def get_choice(self, to_choice_dict: dict) -> str:
+ message = to_choice_dict["message"]
+ if message["role"] == "assistant":
+ return message["content"]
+ else:
+ raise ValueError
+
+
+class OllamaMessageGenerate(OllamaMessageChat, metaclass=OllamaMessageMeta):
+ api_type = OllamaMessageAPI.GENERATE
+
+ @property
+ def api_suffix(self) -> str:
+ return "/generate"
+
+ def apply(self, messages: list[dict]) -> dict:
+ content = messages[0]["content"]
+ prompts = []
+ images = []
+ if isinstance(content, list):
+ for msg in content:
+ prompt, image = self._parse_input_msg(msg)
+ if prompt:
+ prompts.append(prompt)
+ if image:
+ images.append(image)
+ else:
+ prompts.append(content)
+ if len(images) > 0:
+ sends = {"model": self.model, "prompt": "\n".join(prompts), "images": images}
+ else:
+ sends = {"model": self.model, "prompt": "\n".join(prompts)}
+ sends.update(self.additional_kwargs)
+ return sends
+
+ def get_choice(self, to_choice_dict: dict) -> str:
+ return to_choice_dict["response"]
+
+
+class OllamaMessageEmbeddings(OllamaMessageBase, metaclass=OllamaMessageMeta):
+ api_type = OllamaMessageAPI.EMBEDDINGS
+
+ @property
+ def api_suffix(self) -> str:
+ return "/embeddings"
+
+ def apply(self, messages: list[dict]) -> dict:
+ content = messages[0]["content"]
+ prompts = [] # NOTE: not support image to embedding
+ if isinstance(content, list):
+ for msg in content:
+ prompt, _ = self._parse_input_msg(msg)
+ if prompt:
+ prompts.append(prompt)
+ else:
+ prompts.append(content)
+ sends = {"model": self.model, "prompt": "\n".join(prompts)}
+ sends.update(self.additional_kwargs)
+ return sends
+
+
+class OllamaMessageEmbed(OllamaMessageEmbeddings, metaclass=OllamaMessageMeta):
+ api_type = OllamaMessageAPI.EMBED
+
+ @property
+ def api_suffix(self) -> str:
+ return "/embed"
+
+ def apply(self, messages: list[dict]) -> dict:
+ content = messages[0]["content"]
+ prompts = [] # NOTE: not support image to embedding
+ if isinstance(content, list):
+ for msg in content:
+ prompt, _ = self._parse_input_msg(msg)
+ if prompt:
+ prompts.append(prompt)
+ else:
+ prompts.append(content)
+ sends = {"model": self.model, "input": prompts}
+ sends.update(self.additional_kwargs)
+ return sends
+
+
@register_provider(LLMType.OLLAMA)
class OllamaLLM(BaseLLM):
"""
@@ -20,67 +193,80 @@ class OllamaLLM(BaseLLM):
"""
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 = TokenCostManager()
+ self.__init_ollama(config)
+
+ @property
+ def _llama_api_inuse(self) -> OllamaMessageAPI:
+ return OllamaMessageAPI.CHAT
+
+ @property
+ def _llama_api_kwargs(self) -> dict:
+ return {"options": {"temperature": 0.3}, "stream": self.config.stream}
def __init_ollama(self, config: LLMConfig):
assert config.base_url, "ollama base url is required!"
self.model = config.model
self.pricing_plan = self.model
-
- def _const_kwargs(self, messages: list[dict], stream: bool = False) -> dict:
- kwargs = {"model": self.model, "messages": messages, "options": {"temperature": 0.3}, "stream": stream}
- return kwargs
-
- def get_choice_text(self, resp: dict) -> str:
- """get the resp content from llm response"""
- assist_msg = resp.get("message", {})
- assert assist_msg.get("role", None) == "assistant"
- return assist_msg.get("content")
+ ollama_message = OllamaMessageMeta.get_message(self._llama_api_inuse)
+ self.ollama_message = ollama_message(model=self.model, **self._llama_api_kwargs)
def get_usage(self, resp: dict) -> dict:
return {"prompt_tokens": resp.get("prompt_eval_count", 0), "completion_tokens": resp.get("eval_count", 0)}
- def _decode_and_load(self, chunk: bytes, encoding: str = "utf-8") -> dict:
- chunk = chunk.decode(encoding)
- return json.loads(chunk)
-
async def _achat_completion(self, messages: list[dict], timeout: int = USE_CONFIG_TIMEOUT) -> dict:
resp, _, _ = await self.client.arequest(
method=self.http_method,
- url=self.suffix_url,
- params=self._const_kwargs(messages),
+ url=self.ollama_message.api_suffix,
+ params=self.ollama_message.apply(messages=messages),
request_timeout=self.get_timeout(timeout),
)
- resp = self._decode_and_load(resp)
- usage = self.get_usage(resp)
- self._update_costs(usage)
- return resp
+ if isinstance(resp, AsyncGenerator):
+ return await self._processing_openai_response_async_generator(resp)
+ elif isinstance(resp, OpenAIResponse):
+ return self._processing_openai_response(resp)
+ else:
+ raise ValueError
+
+ def get_choice_text(self, rsp):
+ return self.ollama_message.get_choice(rsp)
async def acompletion(self, messages: list[dict], timeout=USE_CONFIG_TIMEOUT) -> dict:
return await self._achat_completion(messages, timeout=self.get_timeout(timeout))
async def _achat_completion_stream(self, messages: list[dict], timeout: int = USE_CONFIG_TIMEOUT) -> str:
- stream_resp, _, _ = await self.client.arequest(
+ resp, _, _ = await self.client.arequest(
method=self.http_method,
- url=self.suffix_url,
- stream=True,
- params=self._const_kwargs(messages, stream=True),
+ url=self.ollama_message.api_suffix,
+ params=self.ollama_message.apply(messages=messages),
request_timeout=self.get_timeout(timeout),
+ stream=True,
)
+ if isinstance(resp, AsyncGenerator):
+ return await self._processing_openai_response_async_generator(resp)
+ elif isinstance(resp, OpenAIResponse):
+ return self._processing_openai_response(resp)
+ else:
+ raise ValueError
+ def _processing_openai_response(self, openai_resp: OpenAIResponse):
+ resp = self.ollama_message.decode(openai_resp)
+ usage = self.get_usage(resp)
+ self._update_costs(usage)
+ return resp
+
+ async def _processing_openai_response_async_generator(self, ag_openai_resp: AsyncGenerator[OpenAIResponse, None]):
collected_content = []
usage = {}
- async for raw_chunk in stream_resp:
- chunk = self._decode_and_load(raw_chunk)
+ async for raw_chunk in ag_openai_resp:
+ chunk = self.ollama_message.decode(raw_chunk)
if not chunk.get("done", False):
- content = self.get_choice_text(chunk)
+ content = self.ollama_message.get_choice(chunk)
collected_content.append(content)
log_llm_stream(content)
else:
@@ -91,3 +277,55 @@ class OllamaLLM(BaseLLM):
self._update_costs(usage)
full_content = "".join(collected_content)
return full_content
+
+
+@register_provider(LLMType.OLLAMA_GENERATE)
+class OllamaGenerate(OllamaLLM):
+ @property
+ def _llama_api_inuse(self) -> OllamaMessageAPI:
+ return OllamaMessageAPI.GENERATE
+
+ @property
+ def _llama_api_kwargs(self) -> dict:
+ return {"options": {"temperature": 0.3}, "stream": self.config.stream}
+
+
+@register_provider(LLMType.OLLAMA_EMBEDDINGS)
+class OllamaEmbeddings(OllamaLLM):
+ @property
+ def _llama_api_inuse(self) -> OllamaMessageAPI:
+ return OllamaMessageAPI.EMBEDDINGS
+
+ @property
+ def _llama_api_kwargs(self) -> dict:
+ return {"options": {"temperature": 0.3}}
+
+ @property
+ def _llama_embedding_key(self) -> str:
+ return "embedding"
+
+ async def _achat_completion(self, messages: list[dict], timeout: int = USE_CONFIG_TIMEOUT) -> dict:
+ resp, _, _ = await self.client.arequest(
+ method=self.http_method,
+ url=self.ollama_message.api_suffix,
+ params=self.ollama_message.apply(messages=messages),
+ request_timeout=self.get_timeout(timeout),
+ )
+ return self.ollama_message.decode(resp)[self._llama_embedding_key]
+
+ async def _achat_completion_stream(self, messages: list[dict], timeout: int = USE_CONFIG_TIMEOUT) -> str:
+ return await self._achat_completion(messages, timeout=self.get_timeout(timeout))
+
+ def get_choice_text(self, rsp):
+ return rsp
+
+
+@register_provider(LLMType.OLLAMA_EMBED)
+class OllamaEmbed(OllamaEmbeddings):
+ @property
+ def _llama_api_inuse(self) -> OllamaMessageAPI:
+ return OllamaMessageAPI.EMBED
+
+ @property
+ def _llama_embedding_key(self) -> str:
+ return "embeddings"
diff --git a/metagpt/provider/openai_api.py b/metagpt/provider/openai_api.py
index 8cb503572..da4278524 100644
--- a/metagpt/provider/openai_api.py
+++ b/metagpt/provider/openai_api.py
@@ -35,7 +35,7 @@ from metagpt.utils.cost_manager import CostManager
from metagpt.utils.exceptions import handle_exception
from metagpt.utils.token_counter import (
count_message_tokens,
- count_string_tokens,
+ count_output_tokens,
get_max_completion_tokens,
)
@@ -51,6 +51,7 @@ from metagpt.utils.token_counter import (
LLMType.OPEN_ROUTER,
LLMType.DEEPSEEK,
LLMType.SILICONFLOW,
+ LLMType.OPENROUTER,
]
)
class OpenAILLM(BaseLLM):
@@ -93,6 +94,7 @@ class OpenAILLM(BaseLLM):
)
usage = None
collected_messages = []
+ has_finished = False
async for chunk in response:
chunk_message = chunk.choices[0].delta.content or "" if chunk.choices else "" # extract the message
finish_reason = (
@@ -100,16 +102,22 @@ class OpenAILLM(BaseLLM):
)
log_llm_stream(chunk_message)
collected_messages.append(chunk_message)
- if finish_reason:
- if hasattr(chunk, "usage"):
- # Some services have usage as an attribute of the chunk, such as Fireworks
+ chunk_has_usage = hasattr(chunk, "usage") and chunk.usage
+ if has_finished:
+ # for oneapi, there has a usage chunk after finish_reason not none chunk
+ if chunk_has_usage:
usage = CompletionUsage(**chunk.usage) if isinstance(chunk.usage, dict) else chunk.usage
+ if finish_reason:
+ if chunk_has_usage:
+ # Some services have usage as an attribute of the chunk, such as Fireworks
+ if isinstance(chunk.usage, CompletionUsage):
+ usage = chunk.usage
+ else:
+ usage = CompletionUsage(**chunk.usage)
elif hasattr(chunk.choices[0], "usage"):
# The usage of some services is an attribute of chunk.choices[0], such as Moonshot
usage = CompletionUsage(**chunk.choices[0].usage)
- if "openrouter.ai" in self.config.base_url and hasattr(chunk, "usage") and chunk.usage is not None:
- # due to it get token cost from api
- usage = chunk.usage
+ has_finished = True
log_llm_stream("\n")
full_reply_content = "".join(collected_messages)
@@ -130,6 +138,10 @@ class OpenAILLM(BaseLLM):
"model": self.model,
"timeout": self.get_timeout(timeout),
}
+ if "o1-" in self.model:
+ # compatible to openai o1-series
+ kwargs["temperature"] = 1
+ kwargs.pop("max_tokens")
if extra_kwargs:
kwargs.update(extra_kwargs)
return kwargs
@@ -254,7 +266,7 @@ class OpenAILLM(BaseLLM):
try:
usage.prompt_tokens = count_message_tokens(messages, self.pricing_plan)
- usage.completion_tokens = count_string_tokens(rsp, self.pricing_plan)
+ usage.completion_tokens = count_output_tokens(rsp, self.pricing_plan)
except Exception as e:
logger.warning(f"usage calculation failed: {e}")
diff --git a/metagpt/provider/postprocess/llm_output_postprocess.py b/metagpt/provider/postprocess/llm_output_postprocess.py
index f898ba3d7..2537d114a 100644
--- a/metagpt/provider/postprocess/llm_output_postprocess.py
+++ b/metagpt/provider/postprocess/llm_output_postprocess.py
@@ -13,7 +13,7 @@ def llm_output_postprocess(
"""
default use BasePostProcessPlugin if there is not matched plugin.
"""
- # TODO choose different model's plugin according to the model_name
+ # TODO choose different model's plugin according to the model
postprocess_plugin = BasePostProcessPlugin()
result = postprocess_plugin.run(output=output, schema=schema, req_key=req_key)
diff --git a/metagpt/provider/qianfan_api.py b/metagpt/provider/qianfan_api.py
index 3d78c8bfc..3ada7908d 100644
--- a/metagpt/provider/qianfan_api.py
+++ b/metagpt/provider/qianfan_api.py
@@ -50,6 +50,9 @@ class QianFanLLM(BaseLLM):
else:
raise ValueError("Set the `access_key`&`secret_key` or `api_key`&`secret_key` first")
+ if self.config.base_url:
+ os.environ.setdefault("QIANFAN_BASE_URL", self.config.base_url)
+
support_system_pairs = [
("ERNIE-Bot-4", "completions_pro"), # (model, corresponding-endpoint)
("ERNIE-Bot-8k", "ernie_bot_8k"),
@@ -103,13 +106,13 @@ class QianFanLLM(BaseLLM):
def get_choice_text(self, resp: JsonBody) -> str:
return resp.get("result", "")
- def completion(self, messages: list[dict]) -> JsonBody:
- resp = self.aclient.do(**self._const_kwargs(messages=messages, stream=False))
+ def completion(self, messages: list[dict], timeout: int = USE_CONFIG_TIMEOUT) -> JsonBody:
+ resp = self.aclient.do(**self._const_kwargs(messages=messages, stream=False), request_timeout=timeout)
self._update_costs(resp.body.get("usage", {}))
return resp.body
async def _achat_completion(self, messages: list[dict], timeout: int = USE_CONFIG_TIMEOUT) -> JsonBody:
- resp = await self.aclient.ado(**self._const_kwargs(messages=messages, stream=False))
+ resp = await self.aclient.ado(**self._const_kwargs(messages=messages, stream=False), request_timeout=timeout)
self._update_costs(resp.body.get("usage", {}))
return resp.body
@@ -117,7 +120,7 @@ class QianFanLLM(BaseLLM):
return await self._achat_completion(messages, timeout=self.get_timeout(timeout))
async def _achat_completion_stream(self, messages: list[dict], timeout: int = USE_CONFIG_TIMEOUT) -> str:
- resp = await self.aclient.ado(**self._const_kwargs(messages=messages, stream=True))
+ resp = await self.aclient.ado(**self._const_kwargs(messages=messages, stream=True), request_timeout=timeout)
collected_content = []
usage = {}
async for chunk in resp:
diff --git a/metagpt/provider/spark_api.py b/metagpt/provider/spark_api.py
index 594267259..8a38d99c5 100644
--- a/metagpt/provider/spark_api.py
+++ b/metagpt/provider/spark_api.py
@@ -1,175 +1,95 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
-"""
-@File : spark_api.py
-"""
-import _thread as thread
-import base64
-import datetime
-import hashlib
-import hmac
-import json
-import ssl
-from time import mktime
-from urllib.parse import urlencode, urlparse
-from wsgiref.handlers import format_date_time
-import websocket # 使用websocket_client
+from sparkai.core.messages import _convert_to_message, convert_to_messages
+from sparkai.core.messages.ai import AIMessage
+from sparkai.core.messages.base import BaseMessage
+from sparkai.core.messages.human import HumanMessage
+from sparkai.core.messages.system import SystemMessage
+from sparkai.core.outputs.llm_result import LLMResult
+from sparkai.llm.llm import ChatSparkLLM
from metagpt.configs.llm_config import LLMConfig, LLMType
from metagpt.const import USE_CONFIG_TIMEOUT
-from metagpt.logs import logger
+from metagpt.logs import log_llm_stream
from metagpt.provider.base_llm import BaseLLM
from metagpt.provider.llm_provider_registry import register_provider
+from metagpt.utils.common import any_to_str
+from metagpt.utils.cost_manager import CostManager
+from metagpt.utils.token_counter import SPARK_TOKENS
@register_provider(LLMType.SPARK)
class SparkLLM(BaseLLM):
+ """
+ 用于讯飞星火大模型系列
+ 参考:https://github.com/iflytek/spark-ai-python"""
+
def __init__(self, config: LLMConfig):
self.config = config
- logger.warning("SparkLLM:当前方法无法支持异步运行。当你使用acompletion时,并不能并行访问。")
+ self.cost_manager = CostManager(token_costs=SPARK_TOKENS)
+ self.model = self.config.domain
+ self._init_client()
- def get_choice_text(self, rsp: dict) -> str:
- return rsp["payload"]["choices"]["text"][-1]["content"]
+ def _init_client(self):
+ self.client = ChatSparkLLM(
+ spark_api_url=self.config.base_url,
+ spark_app_id=self.config.app_id,
+ spark_api_key=self.config.api_key,
+ spark_api_secret=self.config.api_secret,
+ spark_llm_domain=self.config.domain,
+ streaming=True,
+ )
- async def _achat_completion_stream(self, messages: list[dict], timeout: int = USE_CONFIG_TIMEOUT) -> str:
- pass
+ def _system_msg(self, msg: str) -> SystemMessage:
+ return _convert_to_message(msg)
- async def acompletion_text(self, messages: list[dict], stream=False, timeout: int = USE_CONFIG_TIMEOUT) -> str:
- # 不支持
- # logger.warning("当前方法无法支持异步运行。当你使用acompletion时,并不能并行访问。")
- w = GetMessageFromWeb(messages, self.config)
- return w.run()
+ def _user_msg(self, msg: str, **kwargs) -> HumanMessage:
+ return _convert_to_message(msg)
+
+ def _assistant_msg(self, msg: str) -> AIMessage:
+ return _convert_to_message(msg)
+
+ def get_choice_text(self, rsp: LLMResult) -> str:
+ return rsp.generations[0][0].text
+
+ def get_usage(self, response: LLMResult):
+ message = response.generations[0][0].message
+ if hasattr(message, "additional_kwargs"):
+ return message.additional_kwargs.get("token_usage", {})
+ else:
+ return {}
async def _achat_completion(self, messages: list[dict], timeout=USE_CONFIG_TIMEOUT):
- pass
+ response = await self.acreate(messages, stream=False)
+ usage = self.get_usage(response)
+ self._update_costs(usage)
+ return response
async def acompletion(self, messages: list[dict], timeout=USE_CONFIG_TIMEOUT):
- # 不支持异步
- w = GetMessageFromWeb(messages, self.config)
- return w.run()
+ return await self._achat_completion(messages, timeout)
+ async def _achat_completion_stream(self, messages: list[dict], timeout: int = USE_CONFIG_TIMEOUT) -> str:
+ response = await self.acreate(messages, stream=True)
+ collected_content = []
+ usage = {}
+ async for chunk in response:
+ collected_content.append(chunk.content)
+ log_llm_stream(chunk.content)
+ if hasattr(chunk, "additional_kwargs"):
+ usage = chunk.additional_kwargs.get("token_usage", {})
-class GetMessageFromWeb:
- class WsParam:
- """
- 该类适合讯飞星火大部分接口的调用。
- 输入 app_id, api_key, api_secret, spark_url以初始化,
- create_url方法返回接口url
- """
+ log_llm_stream("\n")
+ self._update_costs(usage)
+ full_content = "".join(collected_content)
+ return full_content
- # 初始化
- def __init__(self, app_id, api_key, api_secret, spark_url, message=None):
- self.app_id = app_id
- self.api_key = api_key
- self.api_secret = api_secret
- self.host = urlparse(spark_url).netloc
- self.path = urlparse(spark_url).path
- self.spark_url = spark_url
- self.message = message
+ def _extract_assistant_rsp(self, context: list[BaseMessage]) -> str:
+ return "\n".join([i.content for i in context if "AIMessage" in any_to_str(i)])
- # 生成url
- def create_url(self):
- # 生成RFC1123格式的时间戳
- now = datetime.datetime.now()
- date = format_date_time(mktime(now.timetuple()))
-
- # 拼接字符串
- signature_origin = "host: " + self.host + "\n"
- signature_origin += "date: " + date + "\n"
- signature_origin += "GET " + self.path + " HTTP/1.1"
-
- # 进行hmac-sha256进行加密
- signature_sha = hmac.new(
- self.api_secret.encode("utf-8"), signature_origin.encode("utf-8"), digestmod=hashlib.sha256
- ).digest()
-
- signature_sha_base64 = base64.b64encode(signature_sha).decode(encoding="utf-8")
-
- authorization_origin = f'api_key="{self.api_key}", algorithm="hmac-sha256", headers="host date request-line", signature="{signature_sha_base64}"'
-
- authorization = base64.b64encode(authorization_origin.encode("utf-8")).decode(encoding="utf-8")
-
- # 将请求的鉴权参数组合为字典
- v = {"authorization": authorization, "date": date, "host": self.host}
- # 拼接鉴权参数,生成url
- url = self.spark_url + "?" + urlencode(v)
- # 此处打印出建立连接时候的url,参考本demo的时候可取消上方打印的注释,比对相同参数时生成的url与自己代码生成的url是否一致
- return url
-
- def __init__(self, text, config: LLMConfig):
- self.text = text
- self.ret = ""
- 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)
- code = data["header"]["code"]
-
- if code != 0:
- ws.close() # 请求错误,则关闭socket
- logger.critical(f"回答获取失败,响应信息反序列化之后为: {data}")
- return
+ async def acreate(self, messages: list[dict], stream: bool = True):
+ messages = convert_to_messages(messages)
+ if stream:
+ return self.client.astream(messages)
else:
- choices = data["payload"]["choices"]
- # seq = choices["seq"] # 服务端是流式返回,seq为返回的数据序号
- status = choices["status"] # 服务端是流式返回,status用于判断信息是否传送完毕
- content = choices["text"][0]["content"] # 本次接收到的回答文本
- self.ret += content
- if status == 2:
- ws.close()
-
- # 收到websocket错误的处理
- def on_error(self, ws, error):
- # on_message方法处理接收到的信息,出现任何错误,都会调用这个方法
- logger.critical(f"通讯连接出错,【错误提示: {error}】")
-
- # 收到websocket关闭的处理
- def on_close(self, ws, one, two):
- pass
-
- # 处理请求数据
- def gen_params(self):
- data = {
- "header": {"app_id": self.spark_appid, "uid": "1234"},
- "parameter": {
- "chat": {
- # domain为必传参数
- "domain": self.domain,
- # 以下为可微调,非必传参数
- # 注意:官方建议,temperature和top_k修改一个即可
- "max_tokens": 2048, # 默认2048,模型回答的tokens的最大长度,即允许它输出文本的最长字数
- "temperature": 0.5, # 取值为[0,1],默认为0.5。取值越高随机性越强、发散性越高,即相同的问题得到的不同答案的可能性越高
- "top_k": 4, # 取值为[1,6],默认为4。从k个候选中随机选择一个(非等概率)
- }
- },
- "payload": {"message": {"text": self.text}},
- }
- return data
-
- def send(self, ws, *args):
- data = json.dumps(self.gen_params())
- ws.send(data)
-
- # 收到websocket连接建立的处理
- def on_open(self, ws):
- thread.start_new_thread(self.send, (ws,))
-
- # 处理收到的 websocket消息,出现任何错误,调用on_error方法
- def run(self):
- return self._run(self.text)
-
- def _run(self, text_list):
- ws_param = self.WsParam(self.spark_appid, self.spark_api_key, self.spark_api_secret, self.spark_url, text_list)
- ws_url = ws_param.create_url()
-
- websocket.enableTrace(False) # 默认禁用 WebSocket 的跟踪功能
- ws = websocket.WebSocketApp(
- ws_url, on_message=self.on_message, on_error=self.on_error, on_close=self.on_close, on_open=self.on_open
- )
- ws.run_forever(sslopt={"cert_reqs": ssl.CERT_NONE})
- return self.ret
+ return await self.client.agenerate([messages])
diff --git a/metagpt/provider/zhipuai/async_sse_client.py b/metagpt/provider/zhipuai/async_sse_client.py
index 054865652..116d82f71 100644
--- a/metagpt/provider/zhipuai/async_sse_client.py
+++ b/metagpt/provider/zhipuai/async_sse_client.py
@@ -17,7 +17,7 @@ class AsyncSSEClient(object):
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:
- line = chunk.decode("utf-8")
+ line = chunk.data.decode("utf-8")
if line.startswith(":") or not line:
return
diff --git a/metagpt/provider/zhipuai/zhipu_model_api.py b/metagpt/provider/zhipuai/zhipu_model_api.py
index a7d49623a..bff5889eb 100644
--- a/metagpt/provider/zhipuai/zhipu_model_api.py
+++ b/metagpt/provider/zhipuai/zhipu_model_api.py
@@ -40,7 +40,7 @@ class ZhiPuModelAPI(ZhipuAI):
"""async invoke different from raw method `async_invoke` which get the final result by task_id"""
headers = self._default_headers
resp = await self.arequest(stream=False, method="post", headers=headers, kwargs=kwargs)
- resp = resp.decode("utf-8")
+ resp = resp.data.decode("utf-8")
resp = json.loads(resp)
if "error" in resp:
raise RuntimeError(
diff --git a/metagpt/provider/zhipuai_api.py b/metagpt/provider/zhipuai_api.py
index 2db441991..acac44aaf 100644
--- a/metagpt/provider/zhipuai_api.py
+++ b/metagpt/provider/zhipuai_api.py
@@ -43,7 +43,15 @@ class ZhiPuAILLM(BaseLLM):
self.llm = ZhiPuModelAPI(api_key=self.api_key)
def _const_kwargs(self, messages: list[dict], stream: bool = False) -> dict:
- kwargs = {"model": self.model, "messages": messages, "stream": stream, "temperature": 0.3}
+ max_tokens = self.config.max_token if self.config.max_token > 0 else 1024
+ temperature = self.config.temperature if self.config.temperature > 0.0 else 0.3
+ kwargs = {
+ "model": self.model,
+ "max_tokens": max_tokens,
+ "messages": messages,
+ "stream": stream,
+ "temperature": temperature,
+ }
return kwargs
def completion(self, messages: list[dict], timeout=USE_CONFIG_TIMEOUT) -> dict:
diff --git a/metagpt/rag/benchmark/__init__.py b/metagpt/rag/benchmark/__init__.py
new file mode 100644
index 000000000..7f143b9f2
--- /dev/null
+++ b/metagpt/rag/benchmark/__init__.py
@@ -0,0 +1,3 @@
+from metagpt.rag.benchmark.base import RAGBenchmark
+
+__all__ = ["RAGBenchmark"]
diff --git a/metagpt/rag/benchmark/base.py b/metagpt/rag/benchmark/base.py
new file mode 100644
index 000000000..b5d265b35
--- /dev/null
+++ b/metagpt/rag/benchmark/base.py
@@ -0,0 +1,201 @@
+import asyncio
+from typing import List, Tuple, Union
+
+import evaluate
+import jieba
+from llama_index.core.embeddings import BaseEmbedding
+from llama_index.core.evaluation import SemanticSimilarityEvaluator
+from llama_index.core.schema import NodeWithScore
+from pydantic import BaseModel
+
+from metagpt.const import EXAMPLE_BENCHMARK_PATH
+from metagpt.logs import logger
+from metagpt.rag.factories import get_rag_embedding
+from metagpt.utils.common import read_json_file
+
+
+class DatasetInfo(BaseModel):
+ name: str
+ document_files: List[str]
+ gt_info: List[dict]
+
+
+class DatasetConfig(BaseModel):
+ datasets: List[DatasetInfo]
+
+
+class RAGBenchmark:
+ def __init__(
+ self,
+ embed_model: BaseEmbedding = None,
+ ):
+ self.evaluator = SemanticSimilarityEvaluator(
+ embed_model=embed_model or get_rag_embedding(),
+ )
+
+ def set_metrics(
+ self,
+ bleu_avg: float = 0.0,
+ bleu_1: float = 0.0,
+ bleu_2: float = 0.0,
+ bleu_3: float = 0.0,
+ bleu_4: float = 0.0,
+ rouge_l: float = 0.0,
+ semantic_similarity: float = 0.0,
+ recall: float = 0.0,
+ hit_rate: float = 0.0,
+ mrr: float = 0.0,
+ length: float = 0.0,
+ generated_text: str = None,
+ ground_truth_text: str = None,
+ question: str = None,
+ ):
+ metrics = {
+ "bleu-avg": bleu_avg,
+ "bleu-1": bleu_1,
+ "bleu-2": bleu_2,
+ "bleu-3": bleu_3,
+ "bleu-4": bleu_4,
+ "rouge-L": rouge_l,
+ "semantic similarity": semantic_similarity,
+ "recall": recall,
+ "hit_rate": hit_rate,
+ "mrr": mrr,
+ "length": length,
+ }
+
+ log = {
+ "generated_text": generated_text,
+ "ground_truth_text": ground_truth_text,
+ "question": question,
+ }
+
+ return {"metrics": metrics, "log": log}
+
+ def bleu_score(self, response: str, reference: str, with_penalty=False) -> Union[float, Tuple[float]]:
+ f = lambda text: list(jieba.cut(text))
+ bleu = evaluate.load(path="bleu")
+ results = bleu.compute(predictions=[response], references=[[reference]], tokenizer=f)
+
+ bleu_avg = results["bleu"]
+ bleu1 = results["precisions"][0]
+ bleu2 = results["precisions"][1]
+ bleu3 = results["precisions"][2]
+ bleu4 = results["precisions"][3]
+ brevity_penalty = results["brevity_penalty"]
+
+ if with_penalty:
+ return bleu_avg, bleu1, bleu2, bleu3, bleu4
+ else:
+ return 0.0 if brevity_penalty == 0 else bleu_avg / brevity_penalty, bleu1, bleu2, bleu3, bleu4
+
+ def rougel_score(self, response: str, reference: str) -> float:
+ # pip install rouge_score
+ f = lambda text: list(jieba.cut(text))
+ rouge = evaluate.load(path="rouge")
+
+ results = rouge.compute(predictions=[response], references=[[reference]], tokenizer=f, rouge_types=["rougeL"])
+ score = results["rougeL"]
+ return score
+
+ def recall(self, nodes: list[NodeWithScore], reference_docs: list[str]) -> float:
+ if nodes:
+ total_recall = sum(any(node.text in doc for node in nodes) for doc in reference_docs)
+ return total_recall / len(reference_docs)
+ else:
+ return 0.0
+
+ def hit_rate(self, nodes: list[NodeWithScore], reference_docs: list[str]) -> float:
+ if nodes:
+ return 1.0 if any(node.text in doc for doc in reference_docs for node in nodes) else 0.0
+ else:
+ return 0.0
+
+ def mean_reciprocal_rank(self, nodes: list[NodeWithScore], reference_docs: list[str]) -> float:
+ mrr_sum = 0.0
+
+ for i, node in enumerate(nodes, start=1):
+ for doc in reference_docs:
+ if text in doc:
+ mrr_sum += 1.0 / i
+ return mrr_sum
+
+ return mrr_sum
+
+ async def semantic_similarity(self, response: str, reference: str) -> float:
+ result = await self.evaluator.aevaluate(
+ response=response,
+ reference=reference,
+ )
+
+ return result.score
+
+ async def compute_metric(
+ self,
+ response: str = None,
+ reference: str = None,
+ nodes: list[NodeWithScore] = None,
+ reference_doc: list[str] = None,
+ question: str = None,
+ ):
+ recall = self.recall(nodes, reference_doc)
+ bleu_avg, bleu1, bleu2, bleu3, bleu4 = self.bleu_score(response, reference)
+ rouge_l = self.rougel_score(response, reference)
+ hit_rate = self.hit_rate(nodes, reference_doc)
+ mrr = self.mean_reciprocal_rank(nodes, reference_doc)
+
+ similarity = await self.semantic_similarity(response, reference)
+
+ result = self.set_metrics(
+ bleu_avg,
+ bleu1,
+ bleu2,
+ bleu3,
+ bleu4,
+ rouge_l,
+ similarity,
+ recall,
+ hit_rate,
+ mrr,
+ len(response),
+ response,
+ reference,
+ question,
+ )
+
+ return result
+
+ @staticmethod
+ def load_dataset(ds_names: list[str] = ["all"]):
+ infos = read_json_file((EXAMPLE_BENCHMARK_PATH / "dataset_info.json").as_posix())
+ dataset_config = DatasetConfig(
+ datasets=[
+ DatasetInfo(
+ name=name,
+ document_files=[
+ (EXAMPLE_BENCHMARK_PATH / name / file).as_posix() for file in info["document_file"]
+ ],
+ gt_info=read_json_file((EXAMPLE_BENCHMARK_PATH / name / info["gt_file"]).as_posix()),
+ )
+ for dataset_info in infos
+ for name, info in dataset_info.items()
+ if name in ds_names or "all" in ds_names
+ ]
+ )
+
+ return dataset_config
+
+
+if __name__ == "__main__":
+ benchmark = RAGBenchmark()
+ answer = "是的,根据提供的信息,2023年7月20日,应急管理部和财政部确实联合发布了《因灾倒塌、损坏住房恢复重建救助工作规范》的通知。这份《规范》旨在进一步规范因灾倒塌、损坏住房的恢复重建救助相关工作。它明确了地方各级政府负责实施救助工作,应急管理部和财政部则负责统筹指导。地方财政应安排足够的资金,中央财政也会提供适当的补助。救助资金将通过专账管理,并采取特定的管理方式。救助对象是那些因自然灾害导致住房倒塌或损坏,并向政府提出申请且符合条件的受灾家庭。相关部门将组织调查统计救助对象信息,并建立档案。此外,《规范》还强调了资金发放的具体方式和公开透明的要求。"
+ ground_truth = "“启明行动”是为了防控儿童青少年的近视问题,并发布了《防控儿童青少年近视核心知识十条》。"
+ bleu_avg, bleu1, bleu2, bleu3, bleu4 = benchmark.bleu_score(answer, ground_truth)
+ rougeL_score = benchmark.rougel_score(answer, ground_truth)
+ similarity = asyncio.run(benchmark.SemanticSimilarity(answer, ground_truth))
+
+ logger.info(
+ f"BLEU Scores: bleu_avg = {bleu_avg}, bleu1 = {bleu1}, bleu2 = {bleu2}, bleu3 = {bleu3}, bleu4 = {bleu4}, "
+ f"RougeL Score: {rougeL_score}, "
+ f"Semantic Similarity: {similarity}"
+ )
diff --git a/metagpt/rag/engines/simple.py b/metagpt/rag/engines/simple.py
index 8d78fcad7..61200a295 100644
--- a/metagpt/rag/engines/simple.py
+++ b/metagpt/rag/engines/simple.py
@@ -16,6 +16,7 @@ from llama_index.core.llms import LLM
from llama_index.core.node_parser import SentenceSplitter
from llama_index.core.postprocessor.types import BaseNodePostprocessor
from llama_index.core.query_engine import RetrieverQueryEngine
+from llama_index.core.readers.base import BaseReader
from llama_index.core.response_synthesizers import (
BaseSynthesizer,
get_response_synthesizer,
@@ -30,6 +31,7 @@ from llama_index.core.schema import (
TransformComponent,
)
+from metagpt.config2 import config
from metagpt.rag.factories import (
get_index,
get_rag_embedding,
@@ -38,6 +40,7 @@ from metagpt.rag.factories import (
get_retriever,
)
from metagpt.rag.interface import NoEmbedding, RAGObject
+from metagpt.rag.parsers import OmniParse
from metagpt.rag.retrievers.base import (
DeletableRAGRetriever,
ModifiableRAGRetriever,
@@ -51,6 +54,9 @@ from metagpt.rag.schema import (
BaseRetrieverConfig,
BM25RetrieverConfig,
ObjectNode,
+ OmniParseOptions,
+ OmniParseType,
+ ParseResultType,
)
from metagpt.utils.common import import_class
@@ -110,7 +116,10 @@ class SimpleEngine(RetrieverQueryEngine):
if not input_dir and not input_files:
raise ValueError("Must provide either `input_dir` or `input_files`.")
- documents = SimpleDirectoryReader(input_dir=input_dir, input_files=input_files, fs=fs).load_data()
+ file_extractor = cls._get_file_extractor()
+ documents = SimpleDirectoryReader(
+ input_dir=input_dir, input_files=input_files, file_extractor=file_extractor, fs=fs
+ ).load_data()
cls._fix_document_metadata(documents)
transformations = transformations or cls._default_transformations()
@@ -276,6 +285,29 @@ class SimpleEngine(RetrieverQueryEngine):
transformations=transformations,
)
+ @classmethod
+ def _from_nodes(
+ cls,
+ nodes: list[BaseNode],
+ transformations: Optional[list[TransformComponent]] = None,
+ embed_model: BaseEmbedding = None,
+ llm: LLM = None,
+ retriever_configs: list[BaseRetrieverConfig] = None,
+ ranker_configs: list[BaseRankerConfig] = None,
+ ) -> "SimpleEngine":
+ embed_model = cls._resolve_embed_model(embed_model, retriever_configs)
+ llm = llm or get_rag_llm()
+
+ retriever = get_retriever(configs=retriever_configs, nodes=nodes, embed_model=embed_model)
+ rankers = get_rankers(configs=ranker_configs, llm=llm) # Default []
+
+ return cls(
+ retriever=retriever,
+ node_postprocessors=rankers,
+ response_synthesizer=get_response_synthesizer(llm=llm),
+ transformations=transformations,
+ )
+
@classmethod
def _from_index(
cls,
@@ -357,3 +389,23 @@ class SimpleEngine(RetrieverQueryEngine):
@property
def filenames(self) -> Set[str]:
return self._filenames
+
+ @staticmethod
+ def _get_file_extractor() -> dict[str:BaseReader]:
+ """
+ Get the file extractor.
+ Currently, only PDF use OmniParse. Other document types use the built-in reader from llama_index.
+
+ Returns:
+ dict[file_type: BaseReader]
+ """
+ file_extractor: dict[str:BaseReader] = {}
+ if config.omniparse.base_url:
+ pdf_parser = OmniParse(
+ api_key=config.omniparse.api_key,
+ base_url=config.omniparse.base_url,
+ parse_options=OmniParseOptions(parse_type=OmniParseType.PDF, result_type=ParseResultType.MD),
+ )
+ file_extractor[".pdf"] = pdf_parser
+
+ return file_extractor
diff --git a/metagpt/rag/factories/embedding.py b/metagpt/rag/factories/embedding.py
index 19b8b36f6..6212aae25 100644
--- a/metagpt/rag/factories/embedding.py
+++ b/metagpt/rag/factories/embedding.py
@@ -5,6 +5,9 @@ from typing import Any, Optional
from llama_index.core.embeddings import BaseEmbedding
from llama_index.embeddings.azure_openai import AzureOpenAIEmbedding
+from llama_index.embeddings.gemini import GeminiEmbedding
+from llama_index.embeddings.ollama import OllamaEmbedding
+from llama_index.embeddings.openai import OpenAIEmbedding
from metagpt.config2 import Config
from metagpt.configs.embedding_config import EmbeddingType
diff --git a/metagpt/rag/factories/index.py b/metagpt/rag/factories/index.py
index f897af3ad..4e6d6b167 100644
--- a/metagpt/rag/factories/index.py
+++ b/metagpt/rag/factories/index.py
@@ -8,6 +8,7 @@ from llama_index.core.vector_stores.types import BasePydanticVectorStore
from llama_index.vector_stores.chroma import ChromaVectorStore
from llama_index.vector_stores.elasticsearch import ElasticsearchStore
from llama_index.vector_stores.faiss import FaissVectorStore
+from llama_index.vector_stores.milvus import MilvusVectorStore
from metagpt.rag.factories.base import ConfigBasedFactory
from metagpt.rag.schema import (
@@ -17,6 +18,7 @@ from metagpt.rag.schema import (
ElasticsearchIndexConfig,
ElasticsearchKeywordIndexConfig,
FAISSIndexConfig,
+ MilvusIndexConfig,
)
@@ -28,6 +30,7 @@ class RAGIndexFactory(ConfigBasedFactory):
BM25IndexConfig: self._create_bm25,
ElasticsearchIndexConfig: self._create_es,
ElasticsearchKeywordIndexConfig: self._create_es,
+ MilvusIndexConfig: self._create_milvus,
}
super().__init__(creators)
@@ -46,6 +49,11 @@ class RAGIndexFactory(ConfigBasedFactory):
return self._index_from_storage(storage_context=storage_context, config=config, **kwargs)
+ def _create_milvus(self, config: MilvusIndexConfig, **kwargs) -> VectorStoreIndex:
+ vector_store = MilvusVectorStore(collection_name=config.collection_name, uri=config.uri, token=config.token)
+
+ return self._index_from_vector_store(vector_store=vector_store, config=config, **kwargs)
+
def _create_chroma(self, config: ChromaIndexConfig, **kwargs) -> VectorStoreIndex:
db = chromadb.PersistentClient(str(config.persist_path))
chroma_collection = db.get_or_create_collection(config.collection_name, metadata=config.metadata)
diff --git a/metagpt/rag/factories/llm.py b/metagpt/rag/factories/llm.py
index bd252771a..e936e3a45 100644
--- a/metagpt/rag/factories/llm.py
+++ b/metagpt/rag/factories/llm.py
@@ -22,6 +22,8 @@ class RAGLLM(CustomLLM):
"""LlamaIndex's LLM is different from MetaGPT's LLM.
Inherit CustomLLM from llamaindex, making MetaGPT's LLM can be used by LlamaIndex.
+
+ Set context_length or max_token of LLM in config.yaml if you encounter "Calculated available context size -xxx was not non-negative" error.
"""
model_infer: BaseLLM = Field(..., description="The MetaGPT's LLM.")
diff --git a/metagpt/rag/factories/retriever.py b/metagpt/rag/factories/retriever.py
index 6bc8e4ad5..f487d0e7a 100644
--- a/metagpt/rag/factories/retriever.py
+++ b/metagpt/rag/factories/retriever.py
@@ -13,6 +13,7 @@ from llama_index.core.vector_stores.types import BasePydanticVectorStore
from llama_index.vector_stores.chroma import ChromaVectorStore
from llama_index.vector_stores.elasticsearch import ElasticsearchStore
from llama_index.vector_stores.faiss import FaissVectorStore
+from llama_index.vector_stores.milvus import MilvusVectorStore
from metagpt.rag.factories.base import ConfigBasedFactory
from metagpt.rag.retrievers.base import RAGRetriever
@@ -21,6 +22,7 @@ from metagpt.rag.retrievers.chroma_retriever import ChromaRetriever
from metagpt.rag.retrievers.es_retriever import ElasticsearchRetriever
from metagpt.rag.retrievers.faiss_retriever import FAISSRetriever
from metagpt.rag.retrievers.hybrid_retriever import SimpleHybridRetriever
+from metagpt.rag.retrievers.milvus_retriever import MilvusRetriever
from metagpt.rag.schema import (
BaseRetrieverConfig,
BM25RetrieverConfig,
@@ -28,6 +30,7 @@ from metagpt.rag.schema import (
ElasticsearchKeywordRetrieverConfig,
ElasticsearchRetrieverConfig,
FAISSRetrieverConfig,
+ MilvusRetrieverConfig,
)
@@ -57,6 +60,7 @@ class RetrieverFactory(ConfigBasedFactory):
ChromaRetrieverConfig: self._create_chroma_retriever,
ElasticsearchRetrieverConfig: self._create_es_retriever,
ElasticsearchKeywordRetrieverConfig: self._create_es_retriever,
+ MilvusRetrieverConfig: self._create_milvus_retriever,
}
super().__init__(creators)
@@ -77,6 +81,11 @@ class RetrieverFactory(ConfigBasedFactory):
return index.as_retriever()
+ def _create_milvus_retriever(self, config: MilvusRetrieverConfig, **kwargs) -> MilvusRetriever:
+ config.index = self._build_milvus_index(config, **kwargs)
+
+ return MilvusRetriever(**config.model_dump())
+
def _create_faiss_retriever(self, config: FAISSRetrieverConfig, **kwargs) -> FAISSRetriever:
config.index = self._build_faiss_index(config, **kwargs)
@@ -135,6 +144,14 @@ class RetrieverFactory(ConfigBasedFactory):
return self._build_index_from_vector_store(config, vector_store, **kwargs)
+ @get_or_build_index
+ def _build_milvus_index(self, config: MilvusRetrieverConfig, **kwargs) -> VectorStoreIndex:
+ vector_store = MilvusVectorStore(
+ uri=config.uri, collection_name=config.collection_name, token=config.token, dim=config.dimensions
+ )
+
+ return self._build_index_from_vector_store(config, vector_store, **kwargs)
+
@get_or_build_index
def _build_es_index(self, config: ElasticsearchRetrieverConfig, **kwargs) -> VectorStoreIndex:
vector_store = ElasticsearchStore(**config.store_config.model_dump())
diff --git a/metagpt/rag/parsers/__init__.py b/metagpt/rag/parsers/__init__.py
new file mode 100644
index 000000000..03ac0de3a
--- /dev/null
+++ b/metagpt/rag/parsers/__init__.py
@@ -0,0 +1,3 @@
+from metagpt.rag.parsers.omniparse import OmniParse
+
+__all__ = ["OmniParse"]
diff --git a/metagpt/rag/parsers/omniparse.py b/metagpt/rag/parsers/omniparse.py
new file mode 100644
index 000000000..ec08e38f1
--- /dev/null
+++ b/metagpt/rag/parsers/omniparse.py
@@ -0,0 +1,139 @@
+import asyncio
+from fileinput import FileInput
+from pathlib import Path
+from typing import List, Optional, Union
+
+from llama_index.core import Document
+from llama_index.core.async_utils import run_jobs
+from llama_index.core.readers.base import BaseReader
+
+from metagpt.logs import logger
+from metagpt.rag.schema import OmniParseOptions, OmniParseType, ParseResultType
+from metagpt.utils.async_helper import NestAsyncio
+from metagpt.utils.omniparse_client import OmniParseClient
+
+
+class OmniParse(BaseReader):
+ """OmniParse"""
+
+ def __init__(
+ self, api_key: str = None, base_url: str = "http://localhost:8000", parse_options: OmniParseOptions = None
+ ):
+ """
+ Args:
+ api_key: Default None, can be used for authentication later.
+ base_url: OmniParse Base URL for the API.
+ parse_options: Optional settings for OmniParse. Default is OmniParseOptions with default values.
+ """
+ self.parse_options = parse_options or OmniParseOptions()
+ self.omniparse_client = OmniParseClient(api_key, base_url, max_timeout=self.parse_options.max_timeout)
+
+ @property
+ def parse_type(self):
+ return self.parse_options.parse_type
+
+ @property
+ def result_type(self):
+ return self.parse_options.result_type
+
+ @parse_type.setter
+ def parse_type(self, parse_type: Union[str, OmniParseType]):
+ if isinstance(parse_type, str):
+ parse_type = OmniParseType(parse_type)
+ self.parse_options.parse_type = parse_type
+
+ @result_type.setter
+ def result_type(self, result_type: Union[str, ParseResultType]):
+ if isinstance(result_type, str):
+ result_type = ParseResultType(result_type)
+ self.parse_options.result_type = result_type
+
+ async def _aload_data(
+ self,
+ file_path: Union[str, bytes, Path],
+ extra_info: Optional[dict] = None,
+ ) -> List[Document]:
+ """
+ Load data from the input file_path.
+
+ Args:
+ file_path: File path or file byte data.
+ extra_info: Optional dictionary containing additional information.
+
+ Returns:
+ List[Document]
+ """
+ try:
+ if self.parse_type == OmniParseType.PDF:
+ # pdf parse
+ parsed_result = await self.omniparse_client.parse_pdf(file_path)
+ else:
+ # other parse use omniparse_client.parse_document
+ # For compatible byte data, additional filename is required
+ extra_info = extra_info or {}
+ filename = extra_info.get("filename")
+ parsed_result = await self.omniparse_client.parse_document(file_path, bytes_filename=filename)
+
+ # Get the specified structured data based on result_type
+ content = getattr(parsed_result, self.result_type)
+ docs = [
+ Document(
+ text=content,
+ metadata=extra_info or {},
+ )
+ ]
+ except Exception as e:
+ logger.error(f"OMNI Parse Error: {e}")
+ docs = []
+
+ return docs
+
+ async def aload_data(
+ self,
+ file_path: Union[List[FileInput], FileInput],
+ extra_info: Optional[dict] = None,
+ ) -> List[Document]:
+ """
+ Load data from the input file_path.
+
+ Args:
+ file_path: File path or file byte data.
+ extra_info: Optional dictionary containing additional information.
+
+ Notes:
+ This method ultimately calls _aload_data for processing.
+
+ Returns:
+ List[Document]
+ """
+ docs = []
+ if isinstance(file_path, (str, bytes, Path)):
+ # Processing single file
+ docs = await self._aload_data(file_path, extra_info)
+ elif isinstance(file_path, list):
+ # Concurrently process multiple files
+ parse_jobs = [self._aload_data(file_item, extra_info) for file_item in file_path]
+ doc_ret_list = await run_jobs(jobs=parse_jobs, workers=self.parse_options.num_workers)
+ docs = [doc for docs in doc_ret_list for doc in docs]
+ return docs
+
+ def load_data(
+ self,
+ file_path: Union[List[FileInput], FileInput],
+ extra_info: Optional[dict] = None,
+ ) -> List[Document]:
+ """
+ Load data from the input file_path.
+
+ Args:
+ file_path: File path or file byte data.
+ extra_info: Optional dictionary containing additional information.
+
+ Notes:
+ This method ultimately calls aload_data for processing.
+
+ Returns:
+ List[Document]
+ """
+ NestAsyncio.apply_once() # Ensure compatibility with nested async calls
+ return asyncio.run(self.aload_data(file_path, extra_info))
diff --git a/metagpt/rag/retrievers/milvus_retriever.py b/metagpt/rag/retrievers/milvus_retriever.py
new file mode 100644
index 000000000..bcc66330b
--- /dev/null
+++ b/metagpt/rag/retrievers/milvus_retriever.py
@@ -0,0 +1,17 @@
+"""Milvus retriever."""
+
+from llama_index.core.retrievers import VectorIndexRetriever
+from llama_index.core.schema import BaseNode
+
+
+class MilvusRetriever(VectorIndexRetriever):
+ """Milvus retriever."""
+
+ def add_nodes(self, nodes: list[BaseNode], **kwargs) -> None:
+ """Support add nodes."""
+ self._index.insert_nodes(nodes, **kwargs)
+
+ def persist(self, persist_dir: str, **kwargs) -> None:
+ """Support persist.
+
+ Milvus automatically saves, so there is no need to implement."""
diff --git a/metagpt/rag/schema.py b/metagpt/rag/schema.py
index 4180536a3..12e635a1a 100644
--- a/metagpt/rag/schema.py
+++ b/metagpt/rag/schema.py
@@ -70,6 +70,37 @@ class BM25RetrieverConfig(IndexRetrieverConfig):
_no_embedding: bool = PrivateAttr(default=True)
+class MilvusRetrieverConfig(IndexRetrieverConfig):
+ """Config for Milvus-based retrievers."""
+
+ uri: str = Field(default="./milvus_local.db", description="The directory to save data.")
+ collection_name: str = Field(default="metagpt", description="The name of the collection.")
+ token: str = Field(default=None, description="The token for Milvus")
+ metadata: Optional[CollectionMetadata] = Field(
+ default=None, description="Optional metadata to associate with the collection"
+ )
+ dimensions: int = Field(default=0, description="Dimensionality of the vectors for Milvus index construction.")
+
+ _embedding_type_to_dimensions: ClassVar[dict[EmbeddingType, int]] = {
+ EmbeddingType.GEMINI: 768,
+ EmbeddingType.OLLAMA: 4096,
+ }
+
+ @model_validator(mode="after")
+ def check_dimensions(self):
+ if self.dimensions == 0:
+ config = Config.default()
+ self.dimensions = config.embedding.dimensions or self._embedding_type_to_dimensions.get(
+ config.embedding.api_type, 1536
+ )
+ if not config.embedding.dimensions and config.embedding.api_type not in self._embedding_type_to_dimensions:
+ logger.warning(
+ f"You didn't set dimensions in config when using {config.embedding.api_type}, default to 1536"
+ )
+
+ return self
+
+
class ChromaRetrieverConfig(IndexRetrieverConfig):
"""Config for Chroma-based retrievers."""
@@ -181,6 +212,17 @@ class ChromaIndexConfig(VectorIndexConfig):
)
+class MilvusIndexConfig(VectorIndexConfig):
+ """Config for milvus-based index."""
+
+ collection_name: str = Field(default="metagpt", description="The name of the collection.")
+ uri: str = Field(default="./milvus_local.db", description="The uri of the index.")
+ token: Optional[str] = Field(default=None, description="The token of the index.")
+ metadata: Optional[CollectionMetadata] = Field(
+ default=None, description="Optional metadata to associate with the collection"
+ )
+
+
class BM25IndexConfig(BaseIndexConfig):
"""Config for bm25-based index."""
diff --git a/metagpt/roles/assistant.py b/metagpt/roles/assistant.py
index 895fd8385..eed3c9a11 100644
--- a/metagpt/roles/assistant.py
+++ b/metagpt/roles/assistant.py
@@ -136,4 +136,4 @@ class Assistant(Role):
try:
self.memory = BrainMemory(**m)
except Exception as e:
- logger.exception(f"load error:{e}, data:{jsn}")
+ logger.exception(f"load error:{e}, data:{m}")
diff --git a/metagpt/roles/invoice_ocr_assistant.py b/metagpt/roles/invoice_ocr_assistant.py
index a39a48b97..afcc527a3 100644
--- a/metagpt/roles/invoice_ocr_assistant.py
+++ b/metagpt/roles/invoice_ocr_assistant.py
@@ -80,19 +80,17 @@ class InvoiceOCRAssistant(Role):
raise Exception("Invoice file not uploaded")
resp = await todo.run(file_path)
+ actions = list(self.actions)
if len(resp) == 1:
# Single file support for questioning based on OCR recognition results
- self.set_actions([GenerateTable, ReplyQuestion])
+ actions.extend([GenerateTable, ReplyQuestion])
self.orc_data = resp[0]
else:
- self.set_actions([GenerateTable])
-
- self.set_todo(None)
+ actions.append(GenerateTable)
+ self.set_actions(actions)
+ self.rc.max_react_loop = len(self.actions)
content = INVOICE_OCR_SUCCESS
resp = OCRResults(ocr_result=json.dumps(resp))
- msg = Message(content=content, instruct_content=resp)
- self.rc.memory.add(msg)
- return await super().react()
elif isinstance(todo, GenerateTable):
ocr_results: OCRResults = msg.instruct_content
resp = await todo.run(json.loads(ocr_results.ocr_result), self.filename)
diff --git a/metagpt/roles/researcher.py b/metagpt/roles/researcher.py
index 137cfdb4c..8be2ba6f4 100644
--- a/metagpt/roles/researcher.py
+++ b/metagpt/roles/researcher.py
@@ -31,27 +31,15 @@ class Researcher(Role):
goal: str = "Gather information and conduct research"
constraints: str = "Ensure accuracy and relevance of information"
language: str = "en-us"
+ enable_concurrency: bool = True
def __init__(self, **kwargs):
super().__init__(**kwargs)
- 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)
+ self.set_actions([CollectLinks, WebBrowseAndSummarize, ConductResearch])
+ self._set_react_mode(RoleReactMode.BY_ORDER.value, len(self.actions))
if self.language not in ("en-us", "zh-cn"):
logger.warning(f"The language `{self.language}` has not been tested, it may not work.")
- async def _think(self) -> bool:
- if self.rc.todo is None:
- self._set_state(0)
- return True
-
- if self.rc.state + 1 < len(self.states):
- self._set_state(self.rc.state + 1)
- else:
- self.set_todo(None)
- return False
-
async def _act(self) -> Message:
logger.info(f"{self._setting}: to do {self.rc.todo}({self.rc.todo.name})")
todo = self.rc.todo
@@ -70,8 +58,13 @@ class Researcher(Role):
)
elif isinstance(todo, WebBrowseAndSummarize):
links = instruct_content.links
- todos = (todo.run(*url, query=query, system_text=research_system_text) for (query, url) in links.items())
- summaries = await asyncio.gather(*todos)
+ todos = (
+ todo.run(*url, query=query, system_text=research_system_text) for (query, url) in links.items() if url
+ )
+ if self.enable_concurrency:
+ summaries = await asyncio.gather(*todos)
+ else:
+ summaries = [await i for i in todos]
summaries = list((url, summary) for i in summaries for (url, summary) in i.items() if summary)
ret = Message(
content="", instruct_content=Report(topic=topic, summaries=summaries), role=self.profile, cause_by=todo
@@ -119,8 +112,8 @@ class Researcher(Role):
if __name__ == "__main__":
import fire
- async def main(topic: str, language="en-us"):
- role = Researcher(language=language)
+ async def main(topic: str, language: str = "en-us", enable_concurrency: bool = True):
+ role = Researcher(language=language, enable_concurrency=enable_concurrency)
await role.run(topic)
fire.Fire(main)
diff --git a/metagpt/roles/role.py b/metagpt/roles/role.py
index 1851dd20f..49b1d1787 100644
--- a/metagpt/roles/role.py
+++ b/metagpt/roles/role.py
@@ -315,6 +315,11 @@ class Role(BaseRole, SerializationMixin, ContextMixin, BaseModel):
self.llm.cost_manager = self.context.cost_manager
self.set_actions(self.actions) # reset actions to update llm and prefix
+ @property
+ def name(self):
+ """Get the role name"""
+ return self._setting.name
+
def _get_prefix(self):
"""Get the role prefix"""
if self.desc:
@@ -345,6 +350,12 @@ class Role(BaseRole, SerializationMixin, ContextMixin, BaseModel):
self.recovered = False # avoid max_react_loop out of work
return True
+ if self.rc.react_mode == RoleReactMode.BY_ORDER:
+ if self.rc.max_react_loop != len(self.actions):
+ self.rc.max_react_loop = len(self.actions)
+ self._set_state(self.rc.state + 1)
+ return self.rc.state >= 0 and self.rc.state < len(self.actions)
+
prompt = self._get_prefix()
prompt += STATE_TEMPLATE.format(
history=self.rc.history,
@@ -389,8 +400,8 @@ class Role(BaseRole, SerializationMixin, ContextMixin, BaseModel):
"""Prepare new messages for processing from the message buffer and other sources."""
# Read unprocessed messages from the msg buffer.
news = []
- if self.recovered:
- news = [self.latest_observed_msg] if self.latest_observed_msg else []
+ if self.recovered and self.latest_observed_msg:
+ news = self.rc.memory.find_news(observed=[self.latest_observed_msg], k=10)
if not news:
news = self.rc.msg_buffer.pop_all()
# Store the read messages in your own memory to prevent duplicate processing.
@@ -458,21 +469,12 @@ class Role(BaseRole, SerializationMixin, ContextMixin, BaseModel):
actions_taken += 1
return rsp # return output from the last action
- async def _act_by_order(self) -> Message:
- """switch action each time by order defined in _init_actions, i.e. _act (Action1) -> _act (Action2) -> ..."""
- start_idx = self.rc.state if self.rc.state >= 0 else 0 # action to run from recovered state
- rsp = AIMessage(content="No actions taken yet") # return default message if actions=[]
- for i in range(start_idx, len(self.states)):
- self._set_state(i)
- rsp = await self._act()
- return rsp # return output from the last action
-
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."""
-
- # 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)
+ if not self.planner.plan.goal:
+ # 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:
@@ -509,10 +511,8 @@ class Role(BaseRole, SerializationMixin, ContextMixin, BaseModel):
async def react(self) -> Message:
"""Entry to one of three strategies by which Role reacts to the observed Message"""
- if self.rc.react_mode == RoleReactMode.REACT:
+ if self.rc.react_mode == RoleReactMode.REACT or self.rc.react_mode == RoleReactMode.BY_ORDER:
rsp = await self._react()
- elif self.rc.react_mode == RoleReactMode.BY_ORDER:
- rsp = await self._act_by_order()
elif self.rc.react_mode == RoleReactMode.PLAN_AND_ACT:
rsp = await self._plan_and_act()
else:
diff --git a/metagpt/roles/sk_agent.py b/metagpt/roles/sk_agent.py
deleted file mode 100644
index 71df55fcc..000000000
--- a/metagpt/roles/sk_agent.py
+++ /dev/null
@@ -1,87 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-"""
-@Time : 2023/9/13 12:23
-@Author : femto Zheng
-@File : sk_agent.py
-@Modified By: mashenquan, 2023-11-1. In accordance with Chapter 2.2.1 and 2.2.2 of RFC 116, utilize the new message
- distribution feature for message filtering.
-"""
-from typing import Any, Callable, Union
-
-from pydantic import Field
-from semantic_kernel import Kernel
-from semantic_kernel.planning import SequentialPlanner
-from semantic_kernel.planning.action_planner.action_planner import ActionPlanner
-from semantic_kernel.planning.basic_planner import BasicPlanner, Plan
-
-from metagpt.actions import UserRequirement
-from metagpt.actions.execute_task import ExecuteTask
-from metagpt.logs import logger
-from metagpt.roles import Role
-from metagpt.schema import Message
-from metagpt.utils.make_sk_kernel import make_sk_kernel
-
-
-class SkAgent(Role):
- """
- Represents an SkAgent implemented using semantic kernel
-
- Attributes:
- name (str): Name of the SkAgent.
- profile (str): Role profile, default is 'sk_agent'.
- goal (str): Goal of the SkAgent.
- constraints (str): Constraints for the SkAgent.
- """
-
- name: str = "Sunshine"
- profile: str = "sk_agent"
- goal: str = "Execute task based on passed in task description"
- constraints: str = ""
-
- plan: Plan = Field(default=None, exclude=True)
- planner_cls: Any = None
- planner: Union[BasicPlanner, SequentialPlanner, ActionPlanner] = None
- 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)
-
- def __init__(self, **data: Any) -> None:
- """Initializes the Engineer role with given attributes."""
- super().__init__(**data)
- self.set_actions([ExecuteTask()])
- self._watch([UserRequirement])
- self.kernel = make_sk_kernel()
-
- # how funny the interface is inconsistent
- if self.planner_cls == BasicPlanner or self.planner_cls is None:
- self.planner = BasicPlanner()
- elif self.planner_cls in [SequentialPlanner, ActionPlanner]:
- self.planner = self.planner_cls(self.kernel)
- else:
- raise Exception(f"Unsupported planner of type {self.planner_cls}")
-
- self.import_semantic_skill_from_directory = self.kernel.import_semantic_skill_from_directory
- self.import_skill = self.kernel.import_skill
-
- async def _think(self) -> None:
- self._set_state(0)
- # how funny the interface is inconsistent
- if isinstance(self.planner, BasicPlanner):
- self.plan = await self.planner.create_plan_async(self.rc.important_memory[-1].content, self.kernel)
- logger.info(self.plan.generated_plan)
- elif any(isinstance(self.planner, cls) for cls in [SequentialPlanner, ActionPlanner]):
- self.plan = await self.planner.create_plan_async(self.rc.important_memory[-1].content)
-
- async def _act(self) -> Message:
- # how funny the interface is inconsistent
- result = None
- if isinstance(self.planner, BasicPlanner):
- result = await self.planner.execute_plan_async(self.plan, self.kernel)
- elif any(isinstance(self.planner, cls) for cls in [SequentialPlanner, ActionPlanner]):
- result = (await self.plan.invoke_async()).result
- logger.info(result)
-
- msg = Message(content=result, role=self.profile, cause_by=self.rc.todo)
- self.rc.memory.add(msg)
- return msg
diff --git a/metagpt/roles/tutorial_assistant.py b/metagpt/roles/tutorial_assistant.py
index 6cf3a6469..4cd81f9db 100644
--- a/metagpt/roles/tutorial_assistant.py
+++ b/metagpt/roles/tutorial_assistant.py
@@ -56,7 +56,7 @@ class TutorialAssistant(Role):
self.main_title = titles.get("title")
directory = f"{self.main_title}\n"
self.total_content += f"# {self.main_title}"
- actions = list()
+ actions = list(self.actions)
for first_dir in titles.get("directory"):
actions.append(WriteContent(language=self.language, directory=first_dir))
key = list(first_dir.keys())[0]
@@ -64,6 +64,8 @@ class TutorialAssistant(Role):
for second_dir in first_dir[key]:
directory += f" - {second_dir}\n"
self.set_actions(actions)
+ self.rc.max_react_loop = len(self.actions)
+ return Message()
async def _act(self) -> Message:
"""Perform an action as determined by the role.
@@ -77,8 +79,7 @@ class TutorialAssistant(Role):
self.topic = msg.content
resp = await todo.run(topic=self.topic)
logger.info(resp)
- await self._handle_directory(resp)
- return await super().react()
+ return await self._handle_directory(resp)
resp = await todo.run(topic=self.topic)
logger.info(resp)
if self.total_content != "":
diff --git a/metagpt/software_company.py b/metagpt/software_company.py
index f74b61191..508a6a5f3 100644
--- a/metagpt/software_company.py
+++ b/metagpt/software_company.py
@@ -58,6 +58,8 @@ def generate_repo(
if run_tests:
company.hire([QaEngineer()])
+ if n_round < 8:
+ n_round = 8 # If `--run-tests` is enabled, at least 8 rounds are required to run all QA actions.
else:
stg_path = Path(recover_path)
if not stg_path.exists() or not str(stg_path).endswith("team"):
@@ -125,9 +127,10 @@ def startup(
DEFAULT_CONFIG = """# Full Example: https://github.com/geekan/MetaGPT/blob/main/config/config2.example.yaml
# Reflected Code: https://github.com/geekan/MetaGPT/blob/main/metagpt/config2.py
+# Config Docs: https://docs.deepwisdom.ai/main/en/guide/get_started/configuration.html
llm:
- api_type: "openai" # or azure / ollama / open_llm etc. Check LLMType for more options
- model: "gpt-4-turbo-preview" # or gpt-3.5-turbo-1106 / gpt-4-1106-preview
+ api_type: "openai" # or azure / ollama / groq etc.
+ model: "gpt-4-turbo" # or gpt-3.5-turbo
base_url: "https://api.openai.com/v1" # or forward url / other llm url
api_key: "YOUR_API_KEY"
"""
diff --git a/metagpt/team.py b/metagpt/team.py
index c3498b96b..180ecdb06 100644
--- a/metagpt/team.py
+++ b/metagpt/team.py
@@ -119,9 +119,6 @@ class Team(BaseModel):
)
return self.run_project(idea=idea, send_to=send_to)
- def _save(self):
- logger.info(self.model_dump_json())
-
@serialize_decorator
async def run(self, n_round=3, idea="", send_to="", auto_archive=True):
"""Run company until target round or no money"""
@@ -129,11 +126,13 @@ class Team(BaseModel):
self.run_project(idea=idea, send_to=send_to)
while n_round > 0:
- # self._save()
+ if self.env.is_idle:
+ logger.debug("All roles are idle.")
+ break
n_round -= 1
- logger.debug(f"max {n_round=} left.")
self._check_balance()
-
await self.env.run()
+
+ logger.debug(f"max {n_round=} left.")
self.env.archive(auto_archive)
return self.env.history
diff --git a/metagpt/tools/libs/data_preprocess.py b/metagpt/tools/libs/data_preprocess.py
index aa9070689..ef02d8f3a 100644
--- a/metagpt/tools/libs/data_preprocess.py
+++ b/metagpt/tools/libs/data_preprocess.py
@@ -171,7 +171,7 @@ class OneHotEncode(DataPreprocessTool):
def __init__(self, features: list):
self.features = features
- self.model = OneHotEncoder(handle_unknown="ignore", sparse=False)
+ self.model = OneHotEncoder(handle_unknown="ignore", sparse_output=False)
def transform(self, df: pd.DataFrame) -> pd.DataFrame:
ts_data = self.model.transform(df[self.features])
diff --git a/metagpt/tools/search_engine.py b/metagpt/tools/search_engine.py
index 767f4aaba..81629bb02 100644
--- a/metagpt/tools/search_engine.py
+++ b/metagpt/tools/search_engine.py
@@ -6,37 +6,15 @@
@File : search_engine.py
"""
import importlib
-from typing import Callable, Coroutine, Literal, Optional, Union, overload
+from typing import Annotated, Callable, Coroutine, Literal, Optional, Union, overload
-from pydantic import BaseModel, ConfigDict, model_validator
-from semantic_kernel.skill_definition import sk_function
+from pydantic import BaseModel, ConfigDict, Field, model_validator
from metagpt.configs.search_config import SearchConfig
from metagpt.logs import logger
from metagpt.tools import SearchEngineType
-class SkSearchEngine:
- """A search engine class for executing searches.
-
- Attributes:
- search_engine: The search engine instance used for executing searches.
- """
-
- def __init__(self, **kwargs):
- self.search_engine = SearchEngine(**kwargs)
-
- @sk_function(
- description="searches results from Google. Useful when you need to find short "
- "and succinct answers about a specific topic. Input should be a search query.",
- name="searchAsync",
- input_description="search",
- )
- async def run(self, query: str) -> str:
- result = await self.search_engine.run(query)
- return result
-
-
class SearchEngine(BaseModel):
"""A model for configuring and executing searches with different search engines.
@@ -51,7 +29,9 @@ class SearchEngine(BaseModel):
model_config = ConfigDict(arbitrary_types_allowed=True, extra="allow")
engine: SearchEngineType = SearchEngineType.SERPER_GOOGLE
- run_func: Optional[Callable[[str, int, bool], Coroutine[None, None, Union[str, list[str]]]]] = None
+ run_func: Annotated[
+ Optional[Callable[[str, int, bool], Coroutine[None, None, Union[str, list[str]]]]], Field(exclude=True)
+ ] = None
api_key: Optional[str] = None
proxy: Optional[str] = None
diff --git a/metagpt/tools/search_engine_serpapi.py b/metagpt/tools/search_engine_serpapi.py
index b3ccb0649..6c12bb9e9 100644
--- a/metagpt/tools/search_engine_serpapi.py
+++ b/metagpt/tools/search_engine_serpapi.py
@@ -84,8 +84,11 @@ class SerpAPIWrapper(BaseModel):
get_focused = lambda x: {i: j for i, j in x.items() if i in focus}
if "error" in res.keys():
- raise ValueError(f"Got error from SerpAPI: {res['error']}")
- if "answer_box" in res.keys() and "answer" in res["answer_box"].keys():
+ if res["error"] == "Google hasn't returned any results for this query.":
+ toret = "No good search result found"
+ else:
+ raise ValueError(f"Got error from SerpAPI: {res['error']}")
+ elif "answer_box" in res.keys() and "answer" in res["answer_box"].keys():
toret = res["answer_box"]["answer"]
elif "answer_box" in res.keys() and "snippet" in res["answer_box"].keys():
toret = res["answer_box"]["snippet"]
diff --git a/metagpt/tools/web_browser_engine.py b/metagpt/tools/web_browser_engine.py
index a65bf29bc..52c93849e 100644
--- a/metagpt/tools/web_browser_engine.py
+++ b/metagpt/tools/web_browser_engine.py
@@ -3,9 +3,9 @@
from __future__ import annotations
import importlib
-from typing import Any, Callable, Coroutine, Optional, Union, overload
+from typing import Annotated, Any, Callable, Coroutine, Optional, Union, overload
-from pydantic import BaseModel, ConfigDict, model_validator
+from pydantic import BaseModel, ConfigDict, Field, model_validator
from metagpt.configs.browser_config import BrowserConfig
from metagpt.tools import WebBrowserEngineType
@@ -29,7 +29,10 @@ class WebBrowserEngine(BaseModel):
model_config = ConfigDict(arbitrary_types_allowed=True, extra="allow")
engine: WebBrowserEngineType = WebBrowserEngineType.PLAYWRIGHT
- run_func: Optional[Callable[..., Coroutine[Any, Any, Union[WebPage, list[WebPage]]]]] = None
+ run_func: Annotated[
+ Optional[Callable[..., Coroutine[Any, Any, Union[WebPage, list[WebPage]]]]],
+ Field(exclude=True),
+ ] = None
proxy: Optional[str] = None
@model_validator(mode="after")
diff --git a/metagpt/utils/__init__.py b/metagpt/utils/__init__.py
index 26042eb0e..be911f9dd 100644
--- a/metagpt/utils/__init__.py
+++ b/metagpt/utils/__init__.py
@@ -11,7 +11,7 @@ from metagpt.utils.singleton import Singleton
from metagpt.utils.token_counter import (
TOKEN_COSTS,
count_message_tokens,
- count_string_tokens,
+ count_output_tokens,
)
@@ -22,4 +22,5 @@ __all__ = [
"new_transaction_id",
"count_message_tokens",
"count_string_tokens",
+ "count_output_tokens",
]
diff --git a/metagpt/utils/common.py b/metagpt/utils/common.py
index 90f13da23..cf2aa58ba 100644
--- a/metagpt/utils/common.py
+++ b/metagpt/utils/common.py
@@ -609,6 +609,30 @@ def write_json_file(json_file: str, data: Any, encoding: str = "utf-8", indent:
json.dump(data, fout, ensure_ascii=False, indent=indent, default=custom_default)
+def read_jsonl_file(jsonl_file: str, encoding="utf-8") -> list[dict]:
+ if not Path(jsonl_file).exists():
+ raise FileNotFoundError(f"json_file: {jsonl_file} not exist, return []")
+ datas = []
+ with open(jsonl_file, "r", encoding=encoding) as fin:
+ try:
+ for line in fin:
+ data = json.loads(line)
+ datas.append(data)
+ except Exception:
+ raise ValueError(f"read jsonl file: {jsonl_file} failed")
+ return datas
+
+
+def add_jsonl_file(jsonl_file: str, data: list[dict], encoding: str = None):
+ folder_path = Path(jsonl_file).parent
+ if not folder_path.exists():
+ folder_path.mkdir(parents=True, exist_ok=True)
+
+ with open(jsonl_file, "a", encoding=encoding) as fout:
+ for json_item in data:
+ fout.write(json.dumps(json_item) + "\n")
+
+
def read_csv_to_list(curr_file: str, header=False, strip_trail=True):
"""
Reads in a csv file to a list of list. If header is True, it returns a
@@ -752,7 +776,10 @@ def list_files(root: str | Path) -> List[Path]:
def parse_json_code_block(markdown_text: str) -> List[str]:
- json_blocks = re.findall(r"```json(.*?)```", markdown_text, re.DOTALL)
+ json_blocks = (
+ re.findall(r"```json(.*?)```", markdown_text, re.DOTALL) if "```json" in markdown_text else [markdown_text]
+ )
+
return [v.strip() for v in json_blocks]
@@ -1196,3 +1223,21 @@ def generate_fingerprint(text: str) -> str:
fingerprint = sha256.hexdigest()
return fingerprint
+
+
+def download_model(file_url: str, target_folder: Path) -> Path:
+ file_name = file_url.split("/")[-1]
+ file_path = target_folder.joinpath(f"{file_name}")
+ if not file_path.exists():
+ file_path.mkdir(parents=True, exist_ok=True)
+ try:
+ response = requests.get(file_url, stream=True)
+ response.raise_for_status() # 检查请求是否成功
+ # 保存文件
+ with open(file_path, "wb") as f:
+ for chunk in response.iter_content(chunk_size=8192):
+ f.write(chunk)
+ logger.info(f"权重文件已下载并保存至 {file_path}")
+ except requests.exceptions.HTTPError as err:
+ logger.info(f"权重文件下载过程中发生错误: {err}")
+ return file_path
diff --git a/metagpt/utils/dependency_file.py b/metagpt/utils/dependency_file.py
index 0a375051c..d34e7ae9a 100644
--- a/metagpt/utils/dependency_file.py
+++ b/metagpt/utils/dependency_file.py
@@ -91,7 +91,7 @@ class DependencyFile:
try:
key = Path(filename).relative_to(root).as_posix()
except ValueError:
- key = filename
+ key = Path(filename).as_posix()
return set(self._dependencies.get(str(key), {}))
def delete_file(self):
diff --git a/metagpt/utils/di_graph_repository.py b/metagpt/utils/di_graph_repository.py
index f8fabfbdc..8fdcda53a 100644
--- a/metagpt/utils/di_graph_repository.py
+++ b/metagpt/utils/di_graph_repository.py
@@ -115,6 +115,20 @@ class DiGraphRepository(GraphRepository):
self.load_json(data)
def load_json(self, val: str):
+ """
+ Loads a JSON-encoded string representing a graph structure and updates
+ the internal repository (_repo) with the parsed graph.
+
+ Args:
+ val (str): A JSON-encoded string representing a graph structure.
+
+ Returns:
+ self: Returns the instance of the class with the updated _repo attribute.
+
+ Raises:
+ TypeError: If val is not a valid JSON string or cannot be parsed into
+ a valid graph structure.
+ """
if not val:
return self
m = json.loads(val)
diff --git a/metagpt/utils/file.py b/metagpt/utils/file.py
index a3f612bcc..75107c8be 100644
--- a/metagpt/utils/file.py
+++ b/metagpt/utils/file.py
@@ -191,8 +191,8 @@ class File:
@staticmethod
async def _read_omniparse_config() -> Tuple[str, int]:
config = Config.default()
- if config.omniparse and config.omniparse.url:
- return config.omniparse.url, config.omniparse.timeout
+ if config.omniparse and config.omniparse.base_url:
+ return config.omniparse.base_url, config.omniparse.timeout
return "", 0
diff --git a/metagpt/utils/git_repository.py b/metagpt/utils/git_repository.py
index f3d6350bd..35367532f 100644
--- a/metagpt/utils/git_repository.py
+++ b/metagpt/utils/git_repository.py
@@ -104,7 +104,7 @@ class GitRepository:
self._repository = Repo.init(path=Path(local_path))
gitignore_filename = Path(local_path) / ".gitignore"
- ignores = ["__pycache__", "*.pyc"]
+ ignores = ["__pycache__", "*.pyc", ".vs"]
with open(str(gitignore_filename), mode="w") as writer:
writer.write("\n".join(ignores))
self._repository.index.add([".gitignore"])
diff --git a/metagpt/utils/mmdc_playwright.py b/metagpt/utils/mmdc_playwright.py
index cf846a7e9..ad3644a32 100644
--- a/metagpt/utils/mmdc_playwright.py
+++ b/metagpt/utils/mmdc_playwright.py
@@ -56,38 +56,41 @@ async def mermaid_to_file(
await page.wait_for_load_state("networkidle")
await page.wait_for_selector("div#container", state="attached")
- # mermaid_config = {}
+ mermaid_config = {}
background_color = "#ffffff"
- # my_css = ""
+ my_css = ""
await page.evaluate(f'document.body.style.background = "{background_color}";')
- # metadata = await page.evaluate(
- # """async ([definition, mermaidConfig, myCSS, backgroundColor]) => {
- # const { mermaid, zenuml } = globalThis;
- # await mermaid.registerExternalDiagrams([zenuml]);
- # mermaid.initialize({ startOnLoad: false, ...mermaidConfig });
- # const { svg } = await mermaid.render('my-svg', definition, document.getElementById('container'));
- # document.getElementById('container').innerHTML = svg;
- # const svgElement = document.querySelector('svg');
- # svgElement.style.backgroundColor = backgroundColor;
- #
- # if (myCSS) {
- # const style = document.createElementNS('http://www.w3.org/2000/svg', 'style');
- # style.appendChild(document.createTextNode(myCSS));
- # svgElement.appendChild(style);
- # }
- #
- # }""",
- # [mermaid_code, mermaid_config, my_css, background_color],
- # )
+ await page.evaluate(
+ """async ([definition, mermaidConfig, myCSS, backgroundColor]) => {
+ const { mermaid, zenuml } = globalThis;
+ await mermaid.registerExternalDiagrams([zenuml]);
+ mermaid.initialize({ startOnLoad: false, ...mermaidConfig });
+ const { svg } = await mermaid.render('my-svg', definition, document.getElementById('container'));
+ document.getElementById('container').innerHTML = svg;
+ const svgElement = document.querySelector('svg');
+ svgElement.style.backgroundColor = backgroundColor;
+
+ if (myCSS) {
+ const style = document.createElementNS('http://www.w3.org/2000/svg', 'style');
+ style.appendChild(document.createTextNode(myCSS));
+ svgElement.appendChild(style);
+ }
+
+ }""",
+ [mermaid_code, mermaid_config, my_css, background_color],
+ )
if "svg" in suffixes:
svg_xml = await page.evaluate(
"""() => {
- const svg = document.querySelector('svg');
- const xmlSerializer = new XMLSerializer();
- return xmlSerializer.serializeToString(svg);
- }"""
+ const svg = document.querySelector('svg');
+ if (!svg) {
+ throw new Error('SVG element not found');
+ }
+ const xmlSerializer = new XMLSerializer();
+ return xmlSerializer.serializeToString(svg);
+ }"""
)
logger.info(f"Generating {output_file_without_suffix}.svg..")
with open(f"{output_file_without_suffix}.svg", "wb") as f:
diff --git a/metagpt/utils/mmdc_pyppeteer.py b/metagpt/utils/mmdc_pyppeteer.py
index 36b77b5b2..b7aac6a08 100644
--- a/metagpt/utils/mmdc_pyppeteer.py
+++ b/metagpt/utils/mmdc_pyppeteer.py
@@ -60,29 +60,29 @@ async def mermaid_to_file(
await page.goto(mermaid_html_url)
await page.querySelector("div#container")
- # mermaid_config = {}
+ mermaid_config = {}
background_color = "#ffffff"
- # my_css = ""
+ my_css = ""
await page.evaluate(f'document.body.style.background = "{background_color}";')
- # metadata = await page.evaluate(
- # """async ([definition, mermaidConfig, myCSS, backgroundColor]) => {
- # const { mermaid, zenuml } = globalThis;
- # await mermaid.registerExternalDiagrams([zenuml]);
- # mermaid.initialize({ startOnLoad: false, ...mermaidConfig });
- # const { svg } = await mermaid.render('my-svg', definition, document.getElementById('container'));
- # document.getElementById('container').innerHTML = svg;
- # const svgElement = document.querySelector('svg');
- # svgElement.style.backgroundColor = backgroundColor;
- #
- # if (myCSS) {
- # const style = document.createElementNS('http://www.w3.org/2000/svg', 'style');
- # style.appendChild(document.createTextNode(myCSS));
- # svgElement.appendChild(style);
- # }
- # }""",
- # [mermaid_code, mermaid_config, my_css, background_color],
- # )
+ await page.evaluate(
+ """async ([definition, mermaidConfig, myCSS, backgroundColor]) => {
+ const { mermaid, zenuml } = globalThis;
+ await mermaid.registerExternalDiagrams([zenuml]);
+ mermaid.initialize({ startOnLoad: false, ...mermaidConfig });
+ const { svg } = await mermaid.render('my-svg', definition, document.getElementById('container'));
+ document.getElementById('container').innerHTML = svg;
+ const svgElement = document.querySelector('svg');
+ svgElement.style.backgroundColor = backgroundColor;
+
+ if (myCSS) {
+ const style = document.createElementNS('http://www.w3.org/2000/svg', 'style');
+ style.appendChild(document.createTextNode(myCSS));
+ svgElement.appendChild(style);
+ }
+ }""",
+ [mermaid_code, mermaid_config, my_css, background_color],
+ )
if "svg" in suffixes:
svg_xml = await page.evaluate(
diff --git a/metagpt/utils/redis.py b/metagpt/utils/redis.py
index 7a640563a..9f5ef8a92 100644
--- a/metagpt/utils/redis.py
+++ b/metagpt/utils/redis.py
@@ -10,7 +10,7 @@ from __future__ import annotations
import traceback
from datetime import timedelta
-import aioredis # https://aioredis.readthedocs.io/en/latest/getting-started/
+import redis.asyncio as aioredis
from metagpt.configs.redis_config import RedisConfig
from metagpt.logs import logger
diff --git a/metagpt/utils/sanitize.py b/metagpt/utils/sanitize.py
new file mode 100644
index 000000000..a9becbb98
--- /dev/null
+++ b/metagpt/utils/sanitize.py
@@ -0,0 +1,183 @@
+"""
+@Time : 2024/7/24 16:37
+@Author : didi
+@File : utils.py
+@Acknowledgement https://github.com/evalplus/evalplus/blob/master/evalplus/sanitize.py
+"""
+
+import ast
+import traceback
+from enum import Enum
+from typing import Dict, Generator, List, Optional, Set, Tuple
+
+import tree_sitter_python
+from tree_sitter import Language, Node, Parser
+
+
+class NodeType(Enum):
+ CLASS = "class_definition"
+ FUNCTION = "function_definition"
+ IMPORT = ["import_statement", "import_from_statement"]
+ IDENTIFIER = "identifier"
+ ATTRIBUTE = "attribute"
+ RETURN = "return_statement"
+ EXPRESSION = "expression_statement"
+ ASSIGNMENT = "assignment"
+
+
+def traverse_tree(node: Node) -> Generator[Node, None, None]:
+ """
+ Traverse the tree structure starting from the given node.
+
+ :param node: The root node to start the traversal from.
+ :return: A generator object that yields nodes in the tree.
+ """
+ cursor = node.walk()
+ depth = 0
+
+ visited_children = False
+ while True:
+ if not visited_children:
+ yield cursor.node
+ if not cursor.goto_first_child():
+ depth += 1
+ visited_children = True
+ elif cursor.goto_next_sibling():
+ visited_children = False
+ elif not cursor.goto_parent() or depth == 0:
+ break
+ else:
+ depth -= 1
+
+
+def syntax_check(code, verbose=False):
+ try:
+ ast.parse(code)
+ return True
+ except (SyntaxError, MemoryError):
+ if verbose:
+ traceback.print_exc()
+ return False
+
+
+def code_extract(text: str) -> str:
+ lines = text.split("\n")
+ longest_line_pair = (0, 0)
+ longest_so_far = 0
+
+ for i in range(len(lines)):
+ for j in range(i + 1, len(lines)):
+ current_lines = "\n".join(lines[i : j + 1])
+ if syntax_check(current_lines):
+ current_length = sum(1 for line in lines[i : j + 1] if line.strip())
+ if current_length > longest_so_far:
+ longest_so_far = current_length
+ longest_line_pair = (i, j)
+
+ return "\n".join(lines[longest_line_pair[0] : longest_line_pair[1] + 1])
+
+
+def get_definition_name(node: Node) -> str:
+ for child in node.children:
+ if child.type == NodeType.IDENTIFIER.value:
+ return child.text.decode("utf8")
+
+
+def has_return_statement(node: Node) -> bool:
+ traverse_nodes = traverse_tree(node)
+ for node in traverse_nodes:
+ if node.type == NodeType.RETURN.value:
+ return True
+ return False
+
+
+def get_deps(nodes: List[Tuple[str, Node]]) -> Dict[str, Set[str]]:
+ def dfs_get_deps(node: Node, deps: Set[str]) -> None:
+ for child in node.children:
+ if child.type == NodeType.IDENTIFIER.value:
+ deps.add(child.text.decode("utf8"))
+ else:
+ dfs_get_deps(child, deps)
+
+ name2deps = {}
+ for name, node in nodes:
+ deps = set()
+ dfs_get_deps(node, deps)
+ name2deps[name] = deps
+ return name2deps
+
+
+def get_function_dependency(entrypoint: str, call_graph: Dict[str, str]) -> Set[str]:
+ queue = [entrypoint]
+ visited = {entrypoint}
+ while queue:
+ current = queue.pop(0)
+ if current not in call_graph:
+ continue
+ for neighbour in call_graph[current]:
+ if neighbour not in visited:
+ visited.add(neighbour)
+ queue.append(neighbour)
+ return visited
+
+
+def sanitize(code: str, entrypoint: Optional[str] = None) -> str:
+ """
+ Sanitize and extract relevant parts of the given Python code.
+ This function parses the input code, extracts import statements, class and function definitions,
+ and variable assignments. If an entrypoint is provided, it only includes definitions that are
+ reachable from the entrypoint in the call graph.
+
+ :param code: The input Python code as a string.
+ :param entrypoint: Optional name of a function to use as the entrypoint for dependency analysis.
+ :return: A sanitized version of the input code, containing only relevant parts.
+ """
+ code = code_extract(code)
+ code_bytes = bytes(code, "utf8")
+ parser = Parser(Language(tree_sitter_python.language()))
+ tree = parser.parse(code_bytes)
+ class_names = set()
+ function_names = set()
+ variable_names = set()
+
+ root_node = tree.root_node
+ import_nodes = []
+ definition_nodes = []
+
+ for child in root_node.children:
+ if child.type in NodeType.IMPORT.value:
+ import_nodes.append(child)
+ elif child.type == NodeType.CLASS.value:
+ name = get_definition_name(child)
+ if not (name in class_names or name in variable_names or name in function_names):
+ definition_nodes.append((name, child))
+ class_names.add(name)
+ elif child.type == NodeType.FUNCTION.value:
+ name = get_definition_name(child)
+ if not (name in function_names or name in variable_names or name in class_names) and has_return_statement(
+ child
+ ):
+ definition_nodes.append((name, child))
+ function_names.add(get_definition_name(child))
+ elif child.type == NodeType.EXPRESSION.value and child.children[0].type == NodeType.ASSIGNMENT.value:
+ subchild = child.children[0]
+ name = get_definition_name(subchild)
+ if not (name in variable_names or name in function_names or name in class_names):
+ definition_nodes.append((name, subchild))
+ variable_names.add(name)
+
+ if entrypoint:
+ name2deps = get_deps(definition_nodes)
+ reacheable = get_function_dependency(entrypoint, name2deps)
+
+ sanitized_output = b""
+
+ for node in import_nodes:
+ sanitized_output += code_bytes[node.start_byte : node.end_byte] + b"\n"
+
+ for pair in definition_nodes:
+ name, node = pair
+ if entrypoint and name not in reacheable:
+ continue
+ sanitized_output += code_bytes[node.start_byte : node.end_byte] + b"\n"
+ return sanitized_output[:-1].decode("utf8")
diff --git a/metagpt/utils/stream_pipe.py b/metagpt/utils/stream_pipe.py
new file mode 100644
index 000000000..15a1eef1f
--- /dev/null
+++ b/metagpt/utils/stream_pipe.py
@@ -0,0 +1,42 @@
+# -*- coding: utf-8 -*-
+# @Time : 2024/3/27 10:00
+# @Author : leiwu30
+# @File : stream_pipe.py
+# @Version : None
+# @Description : None
+
+import json
+import time
+from multiprocessing import Pipe
+
+
+class StreamPipe:
+ def __init__(self, name=None):
+ self.name = name
+ self.parent_conn, self.child_conn = Pipe()
+ self.finish: bool = False
+
+ format_data = {
+ "id": "chatcmpl-96bVnBOOyPFZZxEoTIGbdpFcVEnur",
+ "object": "chat.completion.chunk",
+ "created": 1711361191,
+ "model": "gpt-3.5-turbo-0125",
+ "system_fingerprint": "fp_3bc1b5746c",
+ "choices": [
+ {"index": 0, "delta": {"role": "assistant", "content": "content"}, "logprobs": None, "finish_reason": None}
+ ],
+ }
+
+ def set_message(self, msg):
+ self.parent_conn.send(msg)
+
+ def get_message(self, timeout: int = 3):
+ if self.child_conn.poll(timeout):
+ return self.child_conn.recv()
+ else:
+ return None
+
+ def msg2stream(self, msg):
+ self.format_data["created"] = int(time.time())
+ self.format_data["choices"][0]["delta"]["content"] = msg
+ return f"data: {json.dumps(self.format_data, ensure_ascii=False)}\n".encode("utf-8")
diff --git a/metagpt/utils/text.py b/metagpt/utils/text.py
index fb8b94232..df0f5ac05 100644
--- a/metagpt/utils/text.py
+++ b/metagpt/utils/text.py
@@ -1,6 +1,6 @@
from typing import Generator, Sequence
-from metagpt.utils.token_counter import TOKEN_MAX, count_string_tokens
+from metagpt.utils.token_counter import TOKEN_MAX, count_output_tokens
def reduce_message_length(
@@ -23,9 +23,9 @@ def reduce_message_length(
Raises:
RuntimeError: If it fails to reduce the concatenated message length.
"""
- max_token = TOKEN_MAX.get(model_name, 2048) - count_string_tokens(system_text, model_name) - reserved
+ max_token = TOKEN_MAX.get(model_name, 2048) - count_output_tokens(system_text, model_name) - reserved
for msg in msgs:
- if count_string_tokens(msg, model_name) < max_token or model_name not in TOKEN_MAX:
+ if count_output_tokens(msg, model_name) < max_token or model_name not in TOKEN_MAX:
return msg
raise RuntimeError("fail to reduce message length")
@@ -54,13 +54,13 @@ def generate_prompt_chunk(
current_token = 0
current_lines = []
- reserved = reserved + count_string_tokens(prompt_template + system_text, model_name)
+ reserved = reserved + count_output_tokens(prompt_template + system_text, model_name)
# 100 is a magic number to ensure the maximum context length is not exceeded
max_token = TOKEN_MAX.get(model_name, 2048) - reserved - 100
while paragraphs:
paragraph = paragraphs.pop(0)
- token = count_string_tokens(paragraph, model_name)
+ token = count_output_tokens(paragraph, model_name)
if current_token + token <= max_token:
current_lines.append(paragraph)
current_token += token
diff --git a/metagpt/utils/token_counter.py b/metagpt/utils/token_counter.py
index b235ceb7b..f710a0e06 100644
--- a/metagpt/utils/token_counter.py
+++ b/metagpt/utils/token_counter.py
@@ -10,8 +10,11 @@ ref3: https://github.com/Significant-Gravitas/Auto-GPT/blob/master/autogpt/llm/t
ref4: https://github.com/hwchase17/langchain/blob/master/langchain/chat_models/openai.py
ref5: https://ai.google.dev/models/gemini
"""
+import anthropic
import tiktoken
+from metagpt.logs import logger
+
TOKEN_COSTS = {
"anthropic/claude-3.5-sonnet": {"prompt": 0.003, "completion": 0.015},
"gpt-3.5-turbo": {"prompt": 0.0015, "completion": 0.002},
@@ -29,18 +32,29 @@ TOKEN_COSTS = {
"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-0125-preview": {"prompt": 0.01, "completion": 0.03},
+ "gpt-4-turbo": {"prompt": 0.01, "completion": 0.03},
+ "gpt-4-turbo-2024-04-09": {"prompt": 0.01, "completion": 0.03},
"gpt-4-vision-preview": {"prompt": 0.01, "completion": 0.03}, # TODO add extra image price calculator
"gpt-4-1106-vision-preview": {"prompt": 0.01, "completion": 0.03},
"gpt-4o": {"prompt": 0.005, "completion": 0.015},
- "gpt-4o-2024-05-13": {"prompt": 0.005, "completion": 0.015},
"gpt-4o-mini": {"prompt": 0.00015, "completion": 0.0006},
"gpt-4o-mini-2024-07-18": {"prompt": 0.00015, "completion": 0.0006},
+ "gpt-4o-2024-05-13": {"prompt": 0.005, "completion": 0.015},
+ "gpt-4o-2024-08-06": {"prompt": 0.0025, "completion": 0.01},
+ "o1-preview": {"prompt": 0.015, "completion": 0.06},
+ "o1-preview-2024-09-12": {"prompt": 0.015, "completion": 0.06},
+ "o1-mini": {"prompt": 0.003, "completion": 0.012},
+ "o1-mini-2024-09-12": {"prompt": 0.003, "completion": 0.012},
"text-embedding-ada-002": {"prompt": 0.0004, "completion": 0.0},
"glm-3-turbo": {"prompt": 0.0007, "completion": 0.0007}, # 128k version, prompt + completion tokens=0.005¥/k-tokens
"glm-4": {"prompt": 0.014, "completion": 0.014}, # 128k version, prompt + completion tokens=0.1¥/k-tokens
- "gemini-pro": {"prompt": 0.00025, "completion": 0.0005},
+ "glm-4-flash": {"prompt": 0, "completion": 0},
+ "glm-4-plus": {"prompt": 0.007, "completion": 0.007},
+ "gemini-1.5-flash": {"prompt": 0.000075, "completion": 0.0003},
+ "gemini-1.5-pro": {"prompt": 0.0035, "completion": 0.0105},
+ "gemini-1.0-pro": {"prompt": 0.0005, "completion": 0.0015},
"moonshot-v1-8k": {"prompt": 0.012, "completion": 0.012}, # prompt + completion tokens=0.012¥/k-tokens
"moonshot-v1-32k": {"prompt": 0.024, "completion": 0.024},
"moonshot-v1-128k": {"prompt": 0.06, "completion": 0.06},
@@ -53,7 +67,11 @@ TOKEN_COSTS = {
"claude-2.0": {"prompt": 0.008, "completion": 0.024},
"claude-2.1": {"prompt": 0.008, "completion": 0.024},
"claude-3-sonnet-20240229": {"prompt": 0.003, "completion": 0.015},
+ "claude-3-5-sonnet": {"prompt": 0.003, "completion": 0.015},
+ "claude-3-5-sonnet-v2": {"prompt": 0.003, "completion": 0.015}, # alias of newer 3.5 sonnet
+ "claude-3-5-sonnet-20240620": {"prompt": 0.003, "completion": 0.015},
"claude-3-opus-20240229": {"prompt": 0.015, "completion": 0.075},
+ "claude-3-haiku-20240307": {"prompt": 0.00025, "completion": 0.00125},
"yi-34b-chat-0205": {"prompt": 0.0003, "completion": 0.0003},
"yi-34b-chat-200k": {"prompt": 0.0017, "completion": 0.0017},
"openai/gpt-4": {"prompt": 0.03, "completion": 0.06}, # start, for openrouter
@@ -62,12 +80,30 @@ TOKEN_COSTS = {
"openai/gpt-4o-2024-05-13": {"prompt": 0.005, "completion": 0.015},
"openai/gpt-4o-mini": {"prompt": 0.00015, "completion": 0.0006},
"openai/gpt-4o-mini-2024-07-18": {"prompt": 0.00015, "completion": 0.0006},
- "google/gemini-pro-1.5": {"prompt": 0.0025, "completion": 0.0075},
"google/gemini-flash-1.5": {"prompt": 0.00025, "completion": 0.00075},
"deepseek/deepseek-coder": {"prompt": 0.00014, "completion": 0.00028},
"deepseek/deepseek-chat": {"prompt": 0.00014, "completion": 0.00028}, # end, for openrouter
+ "yi-large": {"prompt": 0.0028, "completion": 0.0028},
+ "microsoft/wizardlm-2-8x22b": {"prompt": 0.00108, "completion": 0.00108}, # for openrouter, start
+ "meta-llama/llama-3-70b-instruct": {"prompt": 0.008, "completion": 0.008},
+ "llama3-70b-8192": {"prompt": 0.0059, "completion": 0.0079},
+ "openai/gpt-3.5-turbo-0125": {"prompt": 0.0005, "completion": 0.0015},
+ "openai/gpt-4-turbo-preview": {"prompt": 0.01, "completion": 0.03},
+ "openai/o1-preview": {"prompt": 0.015, "completion": 0.06},
+ "openai/o1-mini": {"prompt": 0.003, "completion": 0.012},
+ "anthropic/claude-3-opus": {"prompt": 0.015, "completion": 0.075},
+ "google/gemini-pro-1.5": {"prompt": 0.0025, "completion": 0.0075}, # for openrouter, end
"deepseek-chat": {"prompt": 0.00014, "completion": 0.00028},
"deepseek-coder": {"prompt": 0.00014, "completion": 0.00028},
+ # For ark model https://www.volcengine.com/docs/82379/1099320
+ "doubao-lite-4k-240515": {"prompt": 0.000043, "completion": 0.000086},
+ "doubao-lite-32k-240515": {"prompt": 0.000043, "completion": 0.000086},
+ "doubao-lite-128k-240515": {"prompt": 0.00011, "completion": 0.00014},
+ "doubao-pro-4k-240515": {"prompt": 0.00011, "completion": 0.00029},
+ "doubao-pro-32k-240515": {"prompt": 0.00011, "completion": 0.00029},
+ "doubao-pro-128k-240515": {"prompt": 0.0007, "completion": 0.0013},
+ "llama3-70b-llama3-70b-instruct": {"prompt": 0.0, "completion": 0.0},
+ "llama3-8b-llama3-8b-instruct": {"prompt": 0.0, "completion": 0.0},
}
@@ -126,16 +162,41 @@ QIANFAN_ENDPOINT_TOKEN_COSTS = {
"""
DashScope Token price https://help.aliyun.com/zh/dashscope/developer-reference/tongyi-thousand-questions-metering-and-billing
Different model has different detail page. Attention, some model are free for a limited time.
+Some new model published by Alibaba will be prioritized to be released on the Model Studio instead of the Dashscope.
+Token price on Model Studio shows on https://help.aliyun.com/zh/model-studio/getting-started/models#ced16cb6cdfsy
"""
DASHSCOPE_TOKEN_COSTS = {
- "qwen-turbo": {"prompt": 0.0011, "completion": 0.0011},
- "qwen-plus": {"prompt": 0.0028, "completion": 0.0028},
- "qwen-max": {"prompt": 0.0, "completion": 0.0},
- "qwen-max-1201": {"prompt": 0.0, "completion": 0.0},
- "qwen-max-longcontext": {"prompt": 0.0, "completion": 0.0},
+ "qwen2.5-72b-instruct": {"prompt": 0.00057, "completion": 0.0017}, # per 1k tokens
+ "qwen2.5-32b-instruct": {"prompt": 0.0005, "completion": 0.001},
+ "qwen2.5-14b-instruct": {"prompt": 0.00029, "completion": 0.00086},
+ "qwen2.5-7b-instruct": {"prompt": 0.00014, "completion": 0.00029},
+ "qwen2.5-3b-instruct": {"prompt": 0.0, "completion": 0.0},
+ "qwen2.5-1.5b-instruct": {"prompt": 0.0, "completion": 0.0},
+ "qwen2.5-0.5b-instruct": {"prompt": 0.0, "completion": 0.0},
+ "qwen2-72b-instruct": {"prompt": 0.000714, "completion": 0.001428},
+ "qwen2-57b-a14b-instruct": {"prompt": 0.0005, "completion": 0.001},
+ "qwen2-7b-instruct": {"prompt": 0.000143, "completion": 0.000286},
+ "qwen2-1.5b-instruct": {"prompt": 0, "completion": 0},
+ "qwen2-0.5b-instruct": {"prompt": 0, "completion": 0},
+ "qwen1.5-110b-chat": {"prompt": 0.001, "completion": 0.002},
+ "qwen1.5-72b-chat": {"prompt": 0.000714, "completion": 0.001428},
+ "qwen1.5-32b-chat": {"prompt": 0.0005, "completion": 0.001},
+ "qwen1.5-14b-chat": {"prompt": 0.000286, "completion": 0.000571},
+ "qwen1.5-7b-chat": {"prompt": 0.000143, "completion": 0.000286},
+ "qwen1.5-1.8b-chat": {"prompt": 0, "completion": 0},
+ "qwen1.5-0.5b-chat": {"prompt": 0, "completion": 0},
+ "qwen-turbo": {"prompt": 0.00028, "completion": 0.00083},
+ "qwen-long": {"prompt": 0.00007, "completion": 0.00028},
+ "qwen-plus": {"prompt": 0.00055, "completion": 0.00166},
+ "qwen-max": {"prompt": 0.0055, "completion": 0.0166},
+ "qwen-max-0428": {"prompt": 0.0055, "completion": 0.0166},
+ "qwen-max-0403": {"prompt": 0.0055, "completion": 0.0166},
+ "qwen-max-0107": {"prompt": 0.0055, "completion": 0.0166},
+ "qwen-max-1201": {"prompt": 0.0166, "completion": 0.0166},
+ "qwen-max-longcontext": {"prompt": 0.0055, "completion": 0.0166},
"llama2-7b-chat-v2": {"prompt": 0.0, "completion": 0.0},
"llama2-13b-chat-v2": {"prompt": 0.0, "completion": 0.0},
- "qwen-72b-chat": {"prompt": 0.0, "completion": 0.0},
+ "qwen-72b-chat": {"prompt": 0.0028, "completion": 0.0028},
"qwen-14b-chat": {"prompt": 0.0011, "completion": 0.0011},
"qwen-7b-chat": {"prompt": 0.00084, "completion": 0.00084},
"qwen-1.8b-chat": {"prompt": 0.0, "completion": 0.0},
@@ -160,18 +221,33 @@ FIREWORKS_GRADE_TOKEN_COSTS = {
"mixtral-8x7b": {"prompt": 0.4, "completion": 1.6},
}
+# https://console.volcengine.com/ark/region:ark+cn-beijing/model
+DOUBAO_TOKEN_COSTS = {
+ "doubao-lite": {"prompt": 0.000043, "completion": 0.000086},
+ "doubao-lite-128k": {"prompt": 0.00011, "completion": 0.00014},
+ "doubao-pro": {"prompt": 0.00011, "completion": 0.00029},
+ "doubao-pro-128k": {"prompt": 0.00071, "completion": 0.0013},
+ "doubao-pro-256k": {"prompt": 0.00071, "completion": 0.0013},
+}
+
# https://platform.openai.com/docs/models/gpt-4-and-gpt-4-turbo
TOKEN_MAX = {
- "gpt-4o-2024-05-13": 128000,
+ "o1-preview": 128000,
+ "o1-preview-2024-09-12": 128000,
+ "o1-mini": 128000,
+ "o1-mini-2024-09-12": 128000,
"gpt-4o": 128000,
- "gpt-4o-mini": 128000,
+ "gpt-4o-2024-05-13": 128000,
+ "gpt-4o-2024-08-06": 128000,
"gpt-4o-mini-2024-07-18": 128000,
+ "gpt-4o-mini": 128000,
+ "gpt-4-turbo-2024-04-09": 128000,
"gpt-4-0125-preview": 128000,
"gpt-4-turbo-preview": 128000,
"gpt-4-1106-preview": 128000,
+ "gpt-4-turbo": 128000,
"gpt-4-vision-preview": 128000,
"gpt-4-1106-vision-preview": 128000,
- "gpt-4-turbo": 128000,
"gpt-4": 8192,
"gpt-4-0613": 8192,
"gpt-4-32k": 32768,
@@ -186,7 +262,9 @@ TOKEN_MAX = {
"text-embedding-ada-002": 8192,
"glm-3-turbo": 128000,
"glm-4": 128000,
- "gemini-pro": 32768,
+ "gemini-1.5-flash": 1000000,
+ "gemini-1.5-pro": 2000000,
+ "gemini-1.0-pro": 32000,
"moonshot-v1-8k": 8192,
"moonshot-v1-32k": 32768,
"moonshot-v1-128k": 128000,
@@ -200,6 +278,8 @@ TOKEN_MAX = {
"claude-2.1": 200000,
"claude-3-sonnet-20240229": 200000,
"claude-3-opus-20240229": 200000,
+ "claude-3-5-sonnet-20240620": 200000,
+ "claude-3-haiku-20240307": 200000,
"yi-34b-chat-0205": 4000,
"yi-34b-chat-200k": 200000,
"openai/gpt-4": 8192, # start, for openrouter
@@ -208,23 +288,128 @@ TOKEN_MAX = {
"openai/gpt-4o-2024-05-13": 128000,
"openai/gpt-4o-mini": 128000,
"openai/gpt-4o-mini-2024-07-18": 128000,
- "anthropic/claude-3.5-sonnet": 200000,
- "google/gemini-pro-1.5": 2800000,
"google/gemini-flash-1.5": 2800000,
"deepseek/deepseek-coder": 128000,
"deepseek/deepseek-chat": 128000, # end, for openrouter
"deepseek-chat": 128000,
"deepseek-coder": 128000,
"deepseek-ai/DeepSeek-Coder-V2-Instruct": 32000, # siliconflow
+ "yi-large": 16385,
+ "microsoft/wizardlm-2-8x22b": 65536,
+ "meta-llama/llama-3-70b-instruct": 8192,
+ "llama3-70b-8192": 8192,
+ "openai/gpt-3.5-turbo-0125": 16385,
+ "openai/gpt-4-turbo-preview": 128000,
+ "openai/o1-preview": 128000,
+ "openai/o1-mini": 128000,
+ "anthropic/claude-3-opus": 200000,
+ "anthropic/claude-3.5-sonnet": 200000,
+ "google/gemini-pro-1.5": 4000000,
+ "doubao-lite-4k-240515": 4000,
+ "doubao-lite-32k-240515": 32000,
+ "doubao-lite-128k-240515": 128000,
+ "doubao-pro-4k-240515": 4000,
+ "doubao-pro-32k-240515": 32000,
+ "doubao-pro-128k-240515": 128000,
+ # Qwen https://help.aliyun.com/zh/dashscope/developer-reference/tongyi-qianwen-7b-14b-72b-api-detailes?spm=a2c4g.11186623.0.i20
+ "qwen2.5-72b-instruct": 131072,
+ "qwen2.5-32b-instruct": 131072,
+ "qwen2.5-14b-instruct": 131072,
+ "qwen2.5-7b-instruct": 131072,
+ "qwen2.5-3b-instruct": 32768,
+ "qwen2.5-1.5b-instruct": 32768,
+ "qwen2.5-0.5b-instruct": 32768,
+ "qwen2-57b-a14b-instruct": 32768,
+ "qwen2-72b-instruct": 131072,
+ "qwen2-7b-instruct": 32768,
+ "qwen2-1.5b-instruct": 32768,
+ "qwen2-0.5b-instruct": 32768,
+ "qwen1.5-110b-chat": 32000,
+ "qwen1.5-72b-chat": 32000,
+ "qwen1.5-32b-chat": 32000,
+ "qwen1.5-14b-chat": 8000,
+ "qwen1.5-7b-chat": 32000,
+ "qwen1.5-1.8b-chat": 32000,
+ "qwen1.5-0.5b-chat": 32000,
+ "codeqwen1.5-7b-chat": 64000,
+ "qwen-72b-chat": 32000,
+ "qwen-14b-chat": 8000,
+ "qwen-7b-chat": 32000,
+ "qwen-1.8b-longcontext-chat": 32000,
+ "qwen-1.8b-chat": 8000,
+}
+
+# For Amazon Bedrock US region
+# See https://aws.amazon.com/cn/bedrock/pricing/
+
+BEDROCK_TOKEN_COSTS = {
+ "amazon.titan-tg1-large": {"prompt": 0.0008, "completion": 0.0008},
+ "amazon.titan-text-express-v1": {"prompt": 0.0008, "completion": 0.0008},
+ "amazon.titan-text-express-v1:0:8k": {"prompt": 0.0008, "completion": 0.0008},
+ "amazon.titan-text-lite-v1:0:4k": {"prompt": 0.0003, "completion": 0.0004},
+ "amazon.titan-text-lite-v1": {"prompt": 0.0003, "completion": 0.0004},
+ "anthropic.claude-instant-v1": {"prompt": 0.0008, "completion": 0.00024},
+ "anthropic.claude-instant-v1:2:100k": {"prompt": 0.0008, "completion": 0.00024},
+ "anthropic.claude-v1": {"prompt": 0.008, "completion": 0.0024},
+ "anthropic.claude-v2": {"prompt": 0.008, "completion": 0.0024},
+ "anthropic.claude-v2:1": {"prompt": 0.008, "completion": 0.0024},
+ "anthropic.claude-v2:0:18k": {"prompt": 0.008, "completion": 0.0024},
+ "anthropic.claude-v2:1:200k": {"prompt": 0.008, "completion": 0.0024},
+ "anthropic.claude-3-sonnet-20240229-v1:0": {"prompt": 0.003, "completion": 0.015},
+ "anthropic.claude-3-sonnet-20240229-v1:0:28k": {"prompt": 0.003, "completion": 0.015},
+ "anthropic.claude-3-sonnet-20240229-v1:0:200k": {"prompt": 0.003, "completion": 0.015},
+ "anthropic.claude-3-5-sonnet-20240620-v1:0": {"prompt": 0.003, "completion": 0.015},
+ "anthropic.claude-3-haiku-20240307-v1:0": {"prompt": 0.00025, "completion": 0.00125},
+ "anthropic.claude-3-haiku-20240307-v1:0:48k": {"prompt": 0.00025, "completion": 0.00125},
+ "anthropic.claude-3-haiku-20240307-v1:0:200k": {"prompt": 0.00025, "completion": 0.00125},
+ # currently (2024-4-29) only available at US West (Oregon) AWS Region.
+ "anthropic.claude-3-opus-20240229-v1:0": {"prompt": 0.015, "completion": 0.075},
+ "cohere.command-text-v14": {"prompt": 0.0015, "completion": 0.0015},
+ "cohere.command-text-v14:7:4k": {"prompt": 0.0015, "completion": 0.0015},
+ "cohere.command-light-text-v14": {"prompt": 0.0003, "completion": 0.0003},
+ "cohere.command-light-text-v14:7:4k": {"prompt": 0.0003, "completion": 0.0003},
+ "meta.llama2-13b-chat-v1:0:4k": {"prompt": 0.00075, "completion": 0.001},
+ "meta.llama2-13b-chat-v1": {"prompt": 0.00075, "completion": 0.001},
+ "meta.llama2-70b-v1": {"prompt": 0.00195, "completion": 0.00256},
+ "meta.llama2-70b-v1:0:4k": {"prompt": 0.00195, "completion": 0.00256},
+ "meta.llama2-70b-chat-v1": {"prompt": 0.00195, "completion": 0.00256},
+ "meta.llama2-70b-chat-v1:0:4k": {"prompt": 0.00195, "completion": 0.00256},
+ "meta.llama3-8b-instruct-v1:0": {"prompt": 0.0004, "completion": 0.0006},
+ "meta.llama3-70b-instruct-v1:0": {"prompt": 0.00265, "completion": 0.0035},
+ "mistral.mistral-7b-instruct-v0:2": {"prompt": 0.00015, "completion": 0.0002},
+ "mistral.mixtral-8x7b-instruct-v0:1": {"prompt": 0.00045, "completion": 0.0007},
+ "mistral.mistral-large-2402-v1:0": {"prompt": 0.008, "completion": 0.024},
+ "ai21.j2-grande-instruct": {"prompt": 0.0125, "completion": 0.0125},
+ "ai21.j2-jumbo-instruct": {"prompt": 0.0188, "completion": 0.0188},
+ "ai21.j2-mid": {"prompt": 0.0125, "completion": 0.0125},
+ "ai21.j2-mid-v1": {"prompt": 0.0125, "completion": 0.0125},
+ "ai21.j2-ultra": {"prompt": 0.0188, "completion": 0.0188},
+ "ai21.j2-ultra-v1": {"prompt": 0.0188, "completion": 0.0188},
+}
+
+# https://xinghuo.xfyun.cn/sparkapi?scr=price
+SPARK_TOKENS = {
+ "general": {"prompt": 0.0, "completion": 0.0}, # Spark-Lite
+ "generalv2": {"prompt": 0.0188, "completion": 0.0188}, # Spark V2.0
+ "generalv3": {"prompt": 0.0035, "completion": 0.0035}, # Spark Pro
+ "generalv3.5": {"prompt": 0.0035, "completion": 0.0035}, # Spark3.5 Max
}
def count_message_tokens(messages, model="gpt-3.5-turbo-0125"):
"""Return the number of tokens used by a list of messages."""
+ if "claude" in model:
+ # rough estimation for models newer than claude-2.1
+ vo = anthropic.Client()
+ num_tokens = 0
+ for message in messages:
+ for key, value in message.items():
+ num_tokens += vo.count_tokens(str(value))
+ return num_tokens
try:
encoding = tiktoken.encoding_for_model(model)
except KeyError:
- print("Warning: model not found. Using cl100k_base encoding.")
+ logger.info(f"Warning: model {model} not found in tiktoken. Using cl100k_base encoding.")
encoding = tiktoken.get_encoding("cl100k_base")
if model in {
"gpt-3.5-turbo-0613",
@@ -238,15 +423,22 @@ def count_message_tokens(messages, model="gpt-3.5-turbo-0125"):
"gpt-4-32k-0314",
"gpt-4-0613",
"gpt-4-32k-0613",
+ "gpt-4-turbo",
"gpt-4-turbo-preview",
"gpt-4-0125-preview",
"gpt-4-1106-preview",
+ "gpt-4-turbo",
"gpt-4-vision-preview",
"gpt-4-1106-vision-preview",
- "gpt-4o-2024-05-13",
"gpt-4o",
+ "gpt-4o-2024-05-13",
+ "gpt-4o-2024-08-06",
"gpt-4o-mini",
"gpt-4o-mini-2024-07-18",
+ "o1-preview",
+ "o1-preview-2024-09-12",
+ "o1-mini",
+ "o1-mini-2024-09-12",
}:
tokens_per_message = 3 # # every reply is primed with <|start|>assistant<|message|>
tokens_per_name = 1
@@ -254,10 +446,10 @@ def count_message_tokens(messages, model="gpt-3.5-turbo-0125"):
tokens_per_message = 4 # every message follows <|start|>{role/name}\n{content}<|end|>\n
tokens_per_name = -1 # if there's a name, the role is omitted
elif "gpt-3.5-turbo" == model:
- print("Warning: gpt-3.5-turbo may update over time. Returning num tokens assuming gpt-3.5-turbo-0125.")
+ logger.info("Warning: gpt-3.5-turbo may update over time. Returning num tokens assuming gpt-3.5-turbo-0125.")
return count_message_tokens(messages, model="gpt-3.5-turbo-0125")
elif "gpt-4" == model:
- print("Warning: gpt-4 may update over time. Returning num tokens assuming gpt-4-0613.")
+ logger.info("Warning: gpt-4 may update over time. Returning num tokens assuming gpt-4-0613.")
return count_message_tokens(messages, model="gpt-4-0613")
elif "open-llm-model" == model:
"""
@@ -289,21 +481,25 @@ def count_message_tokens(messages, model="gpt-3.5-turbo-0125"):
return num_tokens
-def count_string_tokens(string: str, model_name: str) -> int:
+def count_output_tokens(string: str, model: str) -> int:
"""
Returns the number of tokens in a text string.
Args:
string (str): The text string.
- model_name (str): The name of the encoding to use. (e.g., "gpt-3.5-turbo")
+ model (str): The name of the encoding to use. (e.g., "gpt-3.5-turbo")
Returns:
int: The number of tokens in the text string.
"""
+ if "claude" in model:
+ vo = anthropic.Client()
+ num_tokens = vo.count_tokens(string)
+ return num_tokens
try:
- encoding = tiktoken.encoding_for_model(model_name)
+ encoding = tiktoken.encoding_for_model(model)
except KeyError:
- print("Warning: model not found. Using cl100k_base encoding.")
+ logger.info(f"Warning: model {model} not found in tiktoken. Using cl100k_base encoding.")
encoding = tiktoken.get_encoding("cl100k_base")
return len(encoding.encode(string))
diff --git a/pytest.ini b/pytest.ini
new file mode 100644
index 000000000..854c91115
--- /dev/null
+++ b/pytest.ini
@@ -0,0 +1,61 @@
+[pytest]
+testpaths = tests
+addopts =
+ --continue-on-collection-errors
+ --doctest-modules
+ --cov=./metagpt/
+ --cov-report=xml:cov.xml
+ --cov-report=html:htmlcov
+ --durations=20
+ --ignore=tests/metagpt/test_environment.py
+ --ignore=tests/metagpt/test_llm.py
+ --ignore=tests/metagpt/roles/test_assistant.py
+ --ignore=tests/metagpt/roles/test_engineer.py
+ --ignore=tests/metagpt/roles/test_architect.py
+ --ignore=tests/metagpt/roles/test_invoice_ocr_assistant.py
+ --ignore=tests/metagpt/roles/di/test_data_interpreter.py
+ --ignore=tests/metagpt/actions/test_project_management.py
+ --ignore=tests/metagpt/actions/test_write_code.py
+ --ignore=tests/metagpt/actions/test_write_code_review.py
+ --ignore=tests/metagpt/actions/test_write_prd.py
+ --ignore=tests/metagpt/actions/test_invoice_ocr.py
+ --ignore=tests/metagpt/actions/test_action_multi_llm.py
+ --ignore=tests/metagpt/actions/test_design_api.py
+ --ignore=tests/metagpt/actions/di/test_write_analysis_code.py
+ --ignore=tests/metagpt/tools/test_metagpt_oas3_api_svc.py
+ --ignore=tests/metagpt/tools/test_moderation.py
+ --ignore=tests/metagpt/tools/test_search_engine.py
+ --ignore=tests/metagpt/tools/test_tool_convert.py
+ --ignore=tests/metagpt/tools/test_web_browser_engine_playwright.py
+ --ignore=tests/metagpt/tools/test_web_browser_engine.py
+ --ignore=tests/metagpt/tools/test_web_browser_engine_selenium.py
+ --ignore=tests/metagpt/utils/test_mermaid.py
+ --ignore=tests/metagpt/utils/test_redis.py
+ --ignore=tests/metagpt/utils/test_tree.py
+ --ignore=tests/metagpt/utils/test_text.py
+ --ignore=tests/metagpt/provider/test_bedrock_api.py
+ --ignore=tests/metagpt/provider/test_openai.py
+ --ignore=tests/metagpt/provider/test_ark.py
+ --ignore=tests/metagpt/provider/test_general_api_requestor.py
+ --ignore=tests/metagpt/provider/test_qianfan_api.py
+ --ignore=tests/metagpt/provider/zhipuai/test_async_sse_client.py
+ --ignore=tests/metagpt/provider/zhipuai/test_zhipu_model_api.py
+ --ignore=tests/metagpt/planner/test_action_planner.py
+ --ignore=tests/metagpt/planner/test_basic_planner.py
+ --ignore=tests/metagpt/serialize_deserialize/test_write_code_review.py
+ --ignore=tests/metagpt/serialize_deserialize/test_sk_agent.py
+ --ignore=tests/metagpt/rag/factories/test_embedding.py
+ --ignore=tests/metagpt/rag/factories/test_index.py
+ --ignore=tests/metagpt/rag/factories/test_base.py
+ --ignore=tests/metagpt/rag/factories/test_llm.py
+ --ignore=tests/metagpt/rag/factories/test_ranker.py
+ --ignore=tests/metagpt/rag/factories/test_retriever.py
+ --ignore=tests/metagpt/rag/engines/test_simple.py
+ --ignore=tests/metagpt/memory/test_brain_memory.py
+ --ignore=tests/metagpt/memory/test_longterm_memory.py
+ --ignore=tests/metagpt/memory/test_memory_storage.py
+norecursedirs =
+ tests/metagpt/ext
+ tests/metagpt/environment/android_env
+ tests/metagpt/environment/werewolf_env
+
\ No newline at end of file
diff --git a/requirements.txt b/requirements.txt
index 5aa404e7e..67aabf5dc 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -12,14 +12,14 @@ typer==0.9.0
lancedb==0.4.0
loguru==0.6.0
meilisearch==0.21.0
-numpy==1.24.3
-openai==1.6.1
-openpyxl
+numpy~=1.26.4
+openai~=1.39.0
+openpyxl~=3.1.5
beautifulsoup4==4.12.3
pandas==2.1.1
-pydantic==2.5.3
+pydantic>=2.5.3
#pygame==2.1.3
-#pymilvus==2.2.8
+# pymilvus==2.4.6
# pytest==7.2.2 # test extras require
python_docx==0.8.11
PyYAML==6.0.1
@@ -35,6 +35,9 @@ anthropic==0.18.1
typing-inspect==0.8.0
libcst==1.0.1
qdrant-client==1.7.0
+grpcio~=1.67.0
+grpcio-tools~=1.62.3
+grpcio-status~=1.62.3
# pytest-mock==3.11.1 # test extras require
# open-interpreter==0.1.7; python_version>"3.9" # Conflict with openai 1.x
ta==0.10.2
@@ -42,12 +45,14 @@ semantic-kernel==0.4.3.dev0
wrapt==1.15.0
#aiohttp_jinja2
# azure-cognitiveservices-speech~=1.31.0 # Used by metagpt/tools/azure_tts.py
-#aioboto3~=11.3.0 # Used by metagpt/utils/s3.py
-aioredis~=2.0.1 # Used by metagpt/utils/redis.py
-websocket-client==1.6.2
+#aioboto3~=12.4.0 # Used by metagpt/utils/s3.py
+redis~=5.0.0 # Used by metagpt/utils/redis.py
+curl-cffi~=0.7.0
+httplib2~=0.22.0
+websocket-client~=1.8.0
aiofiles==23.2.1
gitpython==3.1.40
-zhipuai==2.0.1
+zhipuai~=2.1.5
rich==13.6.0
nbclient==0.9.0
nbformat==5.9.2
@@ -58,7 +63,7 @@ typing-extensions==4.9.0
socksio~=1.0.0
gitignore-parser==0.1.9
# connexion[uvicorn]~=3.0.5 # Used by metagpt/tools/openapi_v3_hello.py
-websockets~=11.0
+websockets>=10.0,<12.0
networkx~=3.2.1
google-generativeai==0.4.1
playwright>=1.26 # used at metagpt/tools/libs/web_scraping.py
@@ -66,15 +71,21 @@ anytree
ipywidgets==8.1.1
Pillow
imap_tools==1.5.0 # Used by metagpt/tools/libs/email_login.py
-qianfan==0.3.2
-dashscope==1.14.1
-rank-bm25==0.2.2 # for tool recommendation
-gymnasium==0.29.1
pylint~=3.0.3
pygithub~=2.3
htmlmin
fsspec
grep-ast~=0.3.3 # linter
-tree-sitter~=0.21.3 # linter
unidiff==0.7.5 # used at metagpt/tools/libs/cr.py
+qianfan~=0.4.4
+dashscope~=1.19.3
+rank-bm25==0.2.2 # for tool recommendation
+jieba==0.42.1 # for tool recommendation
+volcengine-python-sdk[ark]~=1.0.94 # Solution for installation error in Windows: https://github.com/volcengine/volcengine-python-sdk/issues/5
+gymnasium==0.29.1
+boto3~=1.34.69
+spark_ai_python~=0.3.30
+agentops
+tree_sitter~=0.23.2
+tree_sitter_python~=0.23.2
httpx==0.27.2
\ No newline at end of file
diff --git a/setup.py b/setup.py
index c8e705bfb..2ffc09ee8 100644
--- a/setup.py
+++ b/setup.py
@@ -27,7 +27,7 @@ extras_require = {
"selenium": ["selenium>4", "webdriver_manager", "beautifulsoup4"],
"search-google": ["google-api-python-client==2.94.0"],
"search-ddg": ["duckduckgo-search~=4.1.1"],
- "ocr": ["paddlepaddle==2.4.2", "paddleocr~=2.7.3", "tabulate==0.9.0"],
+ # "ocr": ["paddlepaddle==2.4.2", "paddleocr~=2.7.3", "tabulate==0.9.0"],
"rag": [
"llama-index-core==0.10.15",
"llama-index-embeddings-azure-openai==0.1.6",
@@ -40,6 +40,10 @@ extras_require = {
"llama-index-vector-stores-faiss==0.1.1",
"llama-index-vector-stores-elasticsearch==0.1.6",
"llama-index-vector-stores-chroma==0.1.6",
+ "llama-index-postprocessor-cohere-rerank==0.1.4",
+ "llama-index-postprocessor-colbert-rerank==0.1.1",
+ "llama-index-postprocessor-flag-embedding-reranker==0.1.2",
+ # "llama-index-vector-stores-milvus==0.1.23",
"docx2txt==0.8",
],
}
@@ -55,9 +59,10 @@ extras_require["test"] = [
"pytest-timeout",
"connexion[uvicorn]~=3.0.5",
"azure-cognitiveservices-speech~=1.31.0",
- "aioboto3~=11.3.0",
+ "aioboto3~=12.4.0",
"gradio==3.0.0",
- "grpcio-status==1.48.2",
+ "google-api-core==2.17.1",
+ "protobuf~=4.25.5",
"pylint==3.0.3",
"pybrowsers",
]
@@ -66,31 +71,53 @@ extras_require["pyppeteer"] = [
"pyppeteer>=1.0.2"
] # pyppeteer is unmaintained and there are conflicts with dependencies
extras_require["dev"] = (["pylint~=3.0.3", "black~=23.3.0", "isort~=5.12.0", "pre-commit~=3.6.0"],)
+extras_require["android_assistant"] = [
+ "pyshine==0.0.9",
+ "opencv-python==4.6.0.66",
+ "protobuf<3.20,>=3.9.2",
+ "modelscope",
+ "tensorflow==2.9.1; os_name == 'linux'",
+ "tensorflow==2.9.1; os_name == 'win32'",
+ "tensorflow-macos==2.9; os_name == 'darwin'",
+ "keras==2.9.0",
+ "torch",
+ "torchvision",
+ "transformers",
+ "opencv-python",
+ "matplotlib",
+ "pycocotools",
+ "SentencePiece",
+ "tf_slim",
+ "tf_keras",
+ "pyclipper",
+ "shapely",
+ "groundingdino-py",
+ "datasets==2.18.0",
+ "clip-openai",
+]
-
-if __name__ == "__main__":
- setup(
- name="metagpt",
- version="0.8.0",
- description="The Multi-Agent Framework",
- long_description=long_description,
- long_description_content_type="text/markdown",
- url="https://github.com/geekan/MetaGPT",
- author="Alexander Wu",
- author_email="alexanderwu@deepwisdom.ai",
- license="MIT",
- keywords="metagpt multi-agent multi-role programming gpt llm metaprogramming",
- packages=find_packages(exclude=["contrib", "docs", "examples", "tests*"]),
- python_requires=">=3.9",
- install_requires=requirements,
- extras_require=extras_require,
- cmdclass={
- "install_mermaid": InstallMermaidCLI,
- },
- entry_points={
- "console_scripts": [
- "metagpt=metagpt.software_company:app",
- ],
- },
- include_package_data=True,
- )
+setup(
+ name="metagpt",
+ version="0.8.1",
+ description="The Multi-Agent Framework",
+ long_description=long_description,
+ long_description_content_type="text/markdown",
+ url="https://github.com/geekan/MetaGPT",
+ author="Alexander Wu",
+ author_email="alexanderwu@deepwisdom.ai",
+ license="MIT",
+ keywords="metagpt multi-agent multi-role programming gpt llm metaprogramming",
+ packages=find_packages(exclude=["contrib", "docs", "examples", "tests*"]),
+ python_requires=">=3.9, <3.12",
+ install_requires=requirements,
+ extras_require=extras_require,
+ cmdclass={
+ "install_mermaid": InstallMermaidCLI,
+ },
+ entry_points={
+ "console_scripts": [
+ "metagpt=metagpt.software_company:app",
+ ],
+ },
+ include_package_data=True,
+)
diff --git a/tests/config2.yaml b/tests/config2.yaml
index 58314eaed..e1eb7cfd2 100644
--- a/tests/config2.yaml
+++ b/tests/config2.yaml
@@ -1,7 +1,7 @@
llm:
base_url: "https://api.openai.com/v1"
api_key: "sk-xxx"
- model: "gpt-3.5-turbo-1106"
+ model: "gpt-3.5-turbo"
search:
api_type: "serpapi"
diff --git a/tests/data/andriod_assistant/.gitignore b/tests/data/andriod_assistant/.gitignore
new file mode 100644
index 000000000..0230c3f46
--- /dev/null
+++ b/tests/data/andriod_assistant/.gitignore
@@ -0,0 +1,2 @@
+!*.png
+unitest_Contacts
\ No newline at end of file
diff --git a/tests/data/andriod_assistant/demo_Contacts/labeled_screenshots/0_labeled.png b/tests/data/andriod_assistant/demo_Contacts/labeled_screenshots/0_labeled.png
new file mode 100644
index 000000000..7e60b9a86
Binary files /dev/null and b/tests/data/andriod_assistant/demo_Contacts/labeled_screenshots/0_labeled.png differ
diff --git a/tests/data/andriod_assistant/demo_Contacts/labeled_screenshots/1_labeled.png b/tests/data/andriod_assistant/demo_Contacts/labeled_screenshots/1_labeled.png
new file mode 100644
index 000000000..c790e863c
Binary files /dev/null and b/tests/data/andriod_assistant/demo_Contacts/labeled_screenshots/1_labeled.png differ
diff --git a/tests/data/andriod_assistant/demo_Contacts/record.txt b/tests/data/andriod_assistant/demo_Contacts/record.txt
new file mode 100644
index 000000000..e0b20e4b3
--- /dev/null
+++ b/tests/data/andriod_assistant/demo_Contacts/record.txt
@@ -0,0 +1,2 @@
+tap(9):::android.view.ViewGroup_1067_236_android.widget.TextView_183_204_Apps_2
+stop
diff --git a/tests/data/andriod_assistant/demo_Contacts/task_desc.txt b/tests/data/andriod_assistant/demo_Contacts/task_desc.txt
new file mode 100644
index 000000000..c7e76d8d7
--- /dev/null
+++ b/tests/data/andriod_assistant/demo_Contacts/task_desc.txt
@@ -0,0 +1 @@
+Create a contact in Contacts App named zjy with a phone number +86 18831933368
\ No newline at end of file
diff --git a/tests/data/config/config2.yaml b/tests/data/config/config2.yaml
new file mode 100644
index 000000000..8c9fc0703
--- /dev/null
+++ b/tests/data/config/config2.yaml
@@ -0,0 +1,27 @@
+llm:
+ api_type: "openai" # or azure / ollama / groq etc.
+ base_url: "YOUR_gpt-3.5-turbo_BASE_URL"
+ api_key: "YOUR_gpt-3.5-turbo_API_KEY"
+ model: "gpt-3.5-turbo" # or gpt-3.5-turbo
+ # proxy: "YOUR_gpt-3.5-turbo_PROXY" # for LLM API requests
+ # timeout: 600 # Optional. If set to 0, default value is 300.
+ # Details: https://azure.microsoft.com/en-us/pricing/details/cognitive-services/openai-service/
+ pricing_plan: "" # Optional. Use for Azure LLM when its model name is not the same as OpenAI's
+
+models:
+ "YOUR_MODEL_NAME_1": # model: "gpt-4-turbo" # or gpt-3.5-turbo
+ api_type: "openai" # or azure / ollama / groq etc.
+ base_url: "YOUR_MODEL_1_BASE_URL"
+ api_key: "YOUR_MODEL_1_API_KEY"
+ # proxy: "YOUR_MODEL_1_PROXY" # for LLM API requests
+ # timeout: 600 # Optional. If set to 0, default value is 300.
+ # Details: https://azure.microsoft.com/en-us/pricing/details/cognitive-services/openai-service/
+ pricing_plan: "" # Optional. Use for Azure LLM when its model name is not the same as OpenAI's
+ "YOUR_MODEL_NAME_2": # model: "gpt-4-turbo" # or gpt-3.5-turbo
+ api_type: "openai" # or azure / ollama / groq etc.
+ base_url: "YOUR_MODEL_2_BASE_URL"
+ api_key: "YOUR_MODEL_2_API_KEY"
+ proxy: "YOUR_MODEL_2_PROXY" # for LLM API requests
+ # timeout: 600 # Optional. If set to 0, default value is 300.
+ # Details: https://azure.microsoft.com/en-us/pricing/details/cognitive-services/openai-service/
+ pricing_plan: "" # Optional. Use for Azure LLM when its model name is not the same as OpenAI's
\ No newline at end of file
diff --git a/tests/data/config/config2_multi_llm.yaml b/tests/data/config/config2_multi_llm.yaml
new file mode 100644
index 000000000..c1f6ecfb5
--- /dev/null
+++ b/tests/data/config/config2_multi_llm.yaml
@@ -0,0 +1,27 @@
+llm:
+ api_type: "openai" # or azure / ollama / groq etc.
+ base_url: "YOUR_gpt-3.5-turbo_BASE_URL"
+ api_key: "YOUR_gpt-3.5-turbo_API_KEY"
+ model: "gpt-3.5-turbo" # or gpt-3.5-turbo
+ # proxy: "YOUR_gpt-3.5-turbo_PROXY" # for LLM API requests
+ # timeout: 600 # Optional. If set to 0, default value is 300.
+ # Details: https://azure.microsoft.com/en-us/pricing/details/cognitive-services/openai-service/
+ pricing_plan: "" # Optional. Use for Azure LLM when its model name is not the same as OpenAI's
+
+models:
+ "YOUR_MODEL_NAME_1": # model: "gpt-4-turbo" # or gpt-3.5-turbo
+ api_type: "openai" # or azure / ollama / groq etc.
+ base_url: "YOUR_MODEL_1_BASE_URL"
+ api_key: "YOUR_MODEL_1_API_KEY"
+ # proxy: "YOUR_MODEL_1_PROXY" # for LLM API requests
+ # timeout: 600 # Optional. If set to 0, default value is 300.
+ # Details: https://azure.microsoft.com/en-us/pricing/details/cognitive-services/openai-service/
+ pricing_plan: "" # Optional. Use for Azure LLM when its model name is not the same as OpenAI's
+ "YOUR_MODEL_NAME_2": # model: "gpt-4-turbo" # or gpt-3.5-turbo
+ api_type: "openai" # or azure / ollama / groq etc.
+ base_url: "YOUR_MODEL_2_BASE_URL"
+ api_key: "YOUR_MODEL_2_API_KEY"
+ proxy: "YOUR_MODEL_2_PROXY" # for LLM API requests
+ # timeout: 600 # Optional. If set to 0, default value is 300.
+ # Details: https://azure.microsoft.com/en-us/pricing/details/cognitive-services/openai-service/
+ pricing_plan: "" # Optional. Use for Azure LLM when its model name is not the same as OpenAI's
diff --git a/tests/data/rsp_cache.json b/tests/data/rsp_cache.json
index 1a200ebd9..6c287a32b 100644
--- a/tests/data/rsp_cache.json
+++ b/tests/data/rsp_cache.json
@@ -5,15 +5,15 @@
"\n## context\n```\nclass UIDesign(Action):\n #Class representing the UI Design action.\n def __init__(self, name, context=None, llm=None):\n super().__init__(name, context, llm) # 需要调用LLM进一步丰富UI设计的prompt\n @parse\n def parse_requirement(self, context: str):\n #Parse UI Design draft from the context using regex.\n pattern = r\"## UI Design draft.*?\n(.*?)## Anything UNCLEAR\"\n return context, pattern\n @parse\n def parse_ui_elements(self, context: str):\n #Parse Selected Elements from the context using regex.\n pattern = r\"## Selected Elements.*?\n(.*?)## HTML Layout\"\n return context, pattern\n @parse\n def parse_css_code(self, context: str):\n pattern = r\"```css.*?\n(.*?)## Anything UNCLEAR\"\n return context, pattern\n @parse\n def parse_html_code(self, context: str):\n pattern = r\"```html.*?\n(.*?)```\"\n return context, pattern\n async def draw_icons(self, context, *args, **kwargs):\n #Draw icons using SDEngine.\n engine = SDEngine()\n icon_prompts = self.parse_ui_elements(context)\n icons = icon_prompts.split(\"\n\")\n icons = [s for s in icons if len(s.strip()) > 0]\n prompts_batch = []\n for icon_prompt in icons:\n # fixme: 添加icon lora\n prompt = engine.construct_payload(icon_prompt + \".\")\n prompts_batch.append(prompt)\n await engine.run_t2i(prompts_batch)\n logger.info(\"Finish icon design using StableDiffusion API\")\n async def _save(self, css_content, html_content):\n save_dir = CONFIG.workspace_path / \"resources\" / \"codes\"\n if not os.path.exists(save_dir):\n os.makedirs(save_dir, exist_ok=True)\n # Save CSS and HTML content to files\n css_file_path = save_dir / \"ui_design.css\"\n html_file_path = save_dir / \"ui_design.html\"\n with open(css_file_path, \"w\") as css_file:\n css_file.write(css_content)\n with open(html_file_path, \"w\") as html_file:\n html_file.write(html_content)\n async def run(self, requirements: list[Message], *args, **kwargs) -> ActionOutput:\n #Run the UI Design action.\n # fixme: update prompt (根据需求细化prompt)\n context = requirements[-1].content\n ui_design_draft = self.parse_requirement(context=context)\n # todo: parse requirements str\n prompt = PROMPT_TEMPLATE.format(context=ui_design_draft, format_example=FORMAT_EXAMPLE)\n logger.info(prompt)\n ui_describe = await self._aask_v1(prompt, \"ui_design\", OUTPUT_MAPPING)\n logger.info(ui_describe.content)\n logger.info(ui_describe.instruct_content)\n css = self.parse_css_code(context=ui_describe.content)\n html = self.parse_html_code(context=ui_describe.content)\n await self._save(css_content=css, html_content=html)\n await self.draw_icons(ui_describe.content)\n return ui_describe\n```\n-----\n## format example\n[CONTENT]\n{\n \"ClassView\": \"classDiagram\n class A {\n -int x\n +int y\n -int speed\n -int direction\n +__init__(x: int, y: int, speed: int, direction: int)\n +change_direction(new_direction: int) None\n +move() None\n }\n \"\n}\n[/CONTENT]\n## nodes: \": # \"\n- ClassView: # Generate the mermaid class diagram corresponding to source code in \"context.\"\n## constraint\n- Language: Please use the same language as the user input.\n- Format: output wrapped inside [CONTENT][/CONTENT] as format example, nothing else.\n## action\nFill in the above nodes(ClassView) based on the format example.\n": "ClassView: str # Generate the mermaid class diagram corresponding to source code in \"context.\"",
"\n## context\n\n### Project Name\n\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 \"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 and enjoyable snake game experience\",\n \"Implement smooth and responsive controls\",\n \"Include different difficulty levels\"\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 increase as I eat food\",\n \"As a player, I want the game to end if the snake collides with itself or the boundaries\",\n \"As a player, I want to be able to choose between different difficulty levels\",\n \"As a player, I want to see a game over message when the game ends\"\n ],\n \"Competitive Analysis\": [\n \"Snake Game A: Simple interface, lacks difficulty levels\",\n \"Snake Game B: Responsive controls, but limited features\",\n \"Snake Game C: Multiple difficulty levels, but outdated UI\"\n ],\n \"Competitive Quadrant Chart\": \"quadrantChart\\n title \\\"Engagement and Features of Snake Games\\\"\\n x-axis \\\"Low Engagement\\\" --> \\\"High Engagement\\\"\\n y-axis \\\"Low Features\\\" --> \\\"High Features\\\"\\n quadrant-1 \\\"Improve Engagement & Features\\\"\\n quadrant-2 \\\"Improve Engagement\\\"\\n quadrant-3 \\\"Improve Features\\\"\\n quadrant-4 \\\"Satisfactory\\\"\\n \\\"Snake Game A\\\": [0.4, 0.2]\\n \\\"Snake Game B\\\": [0.6, 0.4]\\n \\\"Snake Game C\\\": [0.7, 0.6]\\n \\\"Our Snake Game\\\": [0.8, 0.8]\",\n \"Requirement Analysis\": \"\",\n \"Requirement Pool\": [\n [\n \"P0\",\n \"Implement snake movement and collision detection\"\n ],\n [\n \"P0\",\n \"Generate food at random positions\"\n ],\n [\n \"P0\",\n \"Increase score when snake eats food\"\n ],\n [\n \"P1\",\n \"Implement game over condition\"\n ],\n [\n \"P1\",\n \"Allow player to choose difficulty level\"\n ]\n ],\n \"UI Design draft\": \"The game will be displayed in the command line interface (CLI). The snake and food will be represented by characters. The score and game over message will be displayed at the bottom of the screen.\",\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 and enjoyable snake game experience\",\"Implement smooth and responsive controls\",\"Include different difficulty levels\"],\"User Stories\":[\"As a player, I want to control the snake using arrow keys\",\"As a player, I want to see my score increase as I eat food\",\"As a player, I want the game to end if the snake collides with itself or the boundaries\",\"As a player, I want to be able to choose between different difficulty levels\",\"As a player, I want to see a game over message when the game ends\"],\"Competitive Analysis\":[\"Snake Game A: Simple interface, lacks difficulty levels\",\"Snake Game B: Responsive controls, but limited features\",\"Snake Game C: Multiple difficulty levels, but outdated UI\"],\"Competitive Quadrant Chart\":\"quadrantChart\\n title \\\"Engagement and Features of Snake Games\\\"\\n x-axis \\\"Low Engagement\\\" --> \\\"High Engagement\\\"\\n y-axis \\\"Low Features\\\" --> \\\"High Features\\\"\\n quadrant-1 \\\"Improve Engagement & Features\\\"\\n quadrant-2 \\\"Improve Engagement\\\"\\n quadrant-3 \\\"Improve Features\\\"\\n quadrant-4 \\\"Satisfactory\\\"\\n \\\"Snake Game A\\\": [0.4, 0.2]\\n \\\"Snake Game B\\\": [0.6, 0.4]\\n \\\"Snake Game C\\\": [0.7, 0.6]\\n \\\"Our Snake Game\\\": [0.8, 0.8]\",\"Requirement Analysis\":\"\",\"Requirement Pool\":[[\"P0\",\"Implement snake movement and collision detection\"],[\"P0\",\"Generate food at random positions\"],[\"P0\",\"Increase score when snake eats food\"],[\"P1\",\"Implement game over condition\"],[\"P1\",\"Allow player to choose difficulty level\"]],\"UI Design draft\":\"The game will be displayed in the command line interface (CLI). The snake and food will be represented by characters. The score and game over message will be displayed at the bottom of the screen.\",\"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: # 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 \"Implementation approach\": \"We will implement the snake game using Python and the command line interface (CLI). We will analyze the difficult points of the requirements and select the appropriate open-source framework to assist with the game development.\",\n \"File list\": [\n \"main.py\",\n \"game.py\"\n ],\n \"Data structures and interfaces\": \"\\nclassDiagram\\n class SnakeGame {\\n -int score\\n -int difficulty\\n -Snake snake\\n -Food food\\n +start_game()\\n +update_game()\\n +end_game()\\n +change_difficulty(difficulty: int)\\n }\\n class Snake {\\n -List[Position] body\\n -Position direction\\n +move()\\n +change_direction(direction: Position)\\n +check_collision()\\n }\\n class Food {\\n -Position position\\n +generate_food()\\n }\\n class Position {\\n -int x\\n -int y\\n }\\n SnakeGame --> Snake\\n SnakeGame --> Food\\n Snake --> Position\\n Food --> Position\\n\",\n \"Program call flow\": \"\\nsequenceDiagram\\n participant G as SnakeGame\\n participant S as Snake\\n participant F as Food\\n participant P as Position\\n G->>S: start_game()\\n S->>F: generate_food()\\n F-->>S: return food\\n S->>G: update_game()\\n G->>S: move()\\n S->>S: check_collision()\\n S->>G: end_game()\\n G->>G: change_difficulty(difficulty)\\n G-->>S: return score\\n\",\n \"Anything UNCLEAR\": \"\"\n}\n[/CONTENT]",
- "\n## context\n{\"Implementation approach\":\"We will implement the snake game using Python and the command line interface (CLI). We will analyze the difficult points of the requirements and select the appropriate open-source framework to assist with the game development.\",\"File list\":[\"main.py\",\"game.py\"],\"Data structures and interfaces\":\"\\nclassDiagram\\n class SnakeGame {\\n -int score\\n -int difficulty\\n -Snake snake\\n -Food food\\n +start_game()\\n +update_game()\\n +end_game()\\n +change_difficulty(difficulty: int)\\n }\\n class Snake {\\n -List[Position] body\\n -Position direction\\n +move()\\n +change_direction(direction: Position)\\n +check_collision()\\n }\\n class Food {\\n -Position position\\n +generate_food()\\n }\\n class Position {\\n -int x\\n -int y\\n }\\n SnakeGame --> Snake\\n SnakeGame --> Food\\n Snake --> Position\\n Food --> Position\\n\",\"Program call flow\":\"\\nsequenceDiagram\\n participant G as SnakeGame\\n participant S as Snake\\n participant F as Food\\n participant P as Position\\n G->>S: start_game()\\n S->>F: generate_food()\\n F-->>S: return food\\n S->>G: update_game()\\n G->>S: move()\\n S->>S: check_collision()\\n S->>G: end_game()\\n G->>G: change_difficulty(difficulty)\\n G-->>S: return score\\n\",\"Anything UNCLEAR\":\"\"}\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 \"python-dotenv==0.17.1\",\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 \"main.py\",\n \"Contains the main function to start the game\"\n ],\n [\n \"game.py\",\n \"Contains the Game class and related functions\"\n ]\n ],\n \"Task list\": [\n \"game.py\",\n \"main.py\"\n ],\n \"Full API spec\": \"\",\n \"Shared Knowledge\": \"\",\n \"Anything UNCLEAR\": \"\"\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 implement the snake game using Python and the command line interface (CLI). We will analyze the difficult points of the requirements and select the appropriate open-source framework to assist with the game development.\",\"File list\":[\"main.py\",\"game.py\"],\"Data structures and interfaces\":\"\\nclassDiagram\\n class SnakeGame {\\n -int score\\n -int difficulty\\n -Snake snake\\n -Food food\\n +start_game()\\n +update_game()\\n +end_game()\\n +change_difficulty(difficulty: int)\\n }\\n class Snake {\\n -List[Position] body\\n -Position direction\\n +move()\\n +change_direction(direction: Position)\\n +check_collision()\\n }\\n class Food {\\n -Position position\\n +generate_food()\\n }\\n class Position {\\n -int x\\n -int y\\n }\\n SnakeGame --> Snake\\n SnakeGame --> Food\\n Snake --> Position\\n Food --> Position\\n\",\"Program call flow\":\"\\nsequenceDiagram\\n participant G as SnakeGame\\n participant S as Snake\\n participant F as Food\\n participant P as Position\\n G->>S: start_game()\\n S->>F: generate_food()\\n F-->>S: return food\\n S->>G: update_game()\\n G->>S: move()\\n S->>S: check_collision()\\n S->>G: end_game()\\n G->>G: change_difficulty(difficulty)\\n G-->>S: return score\\n\",\"Anything UNCLEAR\":\"\"}\n\n## Tasks\n{\"Required Python packages\":[\"python-dotenv==0.17.1\",\"flask==1.1.2\",\"bcrypt==3.2.0\"],\"Required Other language third-party packages\":[\"No third-party dependencies required\"],\"Logic Analysis\":[[\"main.py\",\"Contains the main function to start the game\"],[\"game.py\",\"Contains the Game class and related functions\"]],\"Task list\":[\"game.py\",\"main.py\"],\"Full API spec\":\"\",\"Shared Knowledge\":\"\",\"Anything UNCLEAR\":\"\"}\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\nfrom typing import List\n\nclass Position:\n def __init__(self, x: int, y: int):\n self.x = x\n self.y = y\n\nclass Snake:\n def __init__(self):\n self.body = []\n self.direction = Position(0, 0)\n\n def move(self):\n # TODO: Implement the logic to move the snake's body based on the current direction\n\n def change_direction(self, direction: Position):\n # TODO: Implement the logic to change the direction of the snake\n\n def check_collision(self):\n # TODO: Implement the logic to check if the snake has collided with itself or the boundaries of the game\n\nclass Food:\n def __init__(self):\n self.position = Position(0, 0)\n\n def generate_food(self):\n # TODO: Implement the logic to generate a new position for the food\n\nclass SnakeGame:\n def __init__(self):\n self.score = 0\n self.difficulty = 1\n self.snake = Snake()\n self.food = Food()\n\n def start_game(self):\n # TODO: Implement the logic to start the game\n\n def update_game(self):\n # TODO: Implement the logic to update the game state\n\n def end_game(self):\n # TODO: Implement the logic to end the game\n\n def change_difficulty(self, difficulty: int):\n # TODO: Implement the logic to change the difficulty of the game\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 implement the snake game using Python and the command line interface (CLI). We will analyze the difficult points of the requirements and select the appropriate open-source framework to assist with the game development.\",\"File list\":[\"main.py\",\"game.py\"],\"Data structures and interfaces\":\"\\nclassDiagram\\n class SnakeGame {\\n -int score\\n -int difficulty\\n -Snake snake\\n -Food food\\n +start_game()\\n +update_game()\\n +end_game()\\n +change_difficulty(difficulty: int)\\n }\\n class Snake {\\n -List[Position] body\\n -Position direction\\n +move()\\n +change_direction(direction: Position)\\n +check_collision()\\n }\\n class Food {\\n -Position position\\n +generate_food()\\n }\\n class Position {\\n -int x\\n -int y\\n }\\n SnakeGame --> Snake\\n SnakeGame --> Food\\n Snake --> Position\\n Food --> Position\\n\",\"Program call flow\":\"\\nsequenceDiagram\\n participant G as SnakeGame\\n participant S as Snake\\n participant F as Food\\n participant P as Position\\n G->>S: start_game()\\n S->>F: generate_food()\\n F-->>S: return food\\n S->>G: update_game()\\n G->>S: move()\\n S->>S: check_collision()\\n S->>G: end_game()\\n G->>G: change_difficulty(difficulty)\\n G-->>S: return score\\n\",\"Anything UNCLEAR\":\"\"}\n\n## Tasks\n{\"Required Python packages\":[\"python-dotenv==0.17.1\",\"flask==1.1.2\",\"bcrypt==3.2.0\"],\"Required Other language third-party packages\":[\"No third-party dependencies required\"],\"Logic Analysis\":[[\"main.py\",\"Contains the main function to start the game\"],[\"game.py\",\"Contains the Game class and related functions\"]],\"Task list\":[\"game.py\",\"main.py\"],\"Full API spec\":\"\",\"Shared Knowledge\":\"\",\"Anything UNCLEAR\":\"\"}\n\n## Code Files\n\n\n\n## Code to be Reviewed: game.py\n```Code\n## game.py\n\nfrom typing import List\n\nclass Position:\n def __init__(self, x: int, y: int):\n self.x = x\n self.y = y\n\nclass Snake:\n def __init__(self):\n self.body = []\n self.direction = Position(0, 0)\n\n def move(self):\n # TODO: Implement the logic to move the snake's body based on the current direction\n\n def change_direction(self, direction: Position):\n # TODO: Implement the logic to change the direction of the snake\n\n def check_collision(self):\n # TODO: Implement the logic to check if the snake has collided with itself or the boundaries of the game\n\nclass Food:\n def __init__(self):\n self.position = Position(0, 0)\n\n def generate_food(self):\n # TODO: Implement the logic to generate a new position for the food\n\nclass SnakeGame:\n def __init__(self):\n self.score = 0\n self.difficulty = 1\n self.snake = Snake()\n self.food = Food()\n\n def start_game(self):\n # TODO: Implement the logic to start the game\n\n def update_game(self):\n # TODO: Implement the logic to update the game state\n\n def end_game(self):\n # TODO: Implement the logic to end the game\n\n def change_difficulty(self, difficulty: int):\n # TODO: Implement the logic to change the difficulty of the game\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. Is the code implemented as per the requirements? If not, how to achieve it? Analyze it step by step.\n - No, the code is not implemented as per the requirements. The logic for moving the snake's body, changing the direction of the snake, checking collision, generating food, starting the game, updating the game state, ending the game, and changing the difficulty of the game is missing. To achieve the requirements, the logic for each of these functions needs to be implemented step by step.\n\n2. Is the code logic completely correct? If there are errors, please indicate how to correct them.\n - No, the code logic is not correct as the functions are not implemented. To correct the logic, each function needs to be implemented with the appropriate logic for the game.\n\n3. Does the existing code follow the \"Data structures and interfaces\"?\n - Yes, the existing code follows the defined data structures and interfaces.\n\n4. Are all functions implemented? If there is no implementation, please indicate how to achieve it step by step.\n - No, all functions are not implemented. The following steps can be followed to implement each function:\n - Snake.move(): Implement the logic to move the snake's body based on the current direction.\n - Snake.change_direction(): Implement the logic to change the direction of the snake.\n - Snake.check_collision(): Implement the logic to check if the snake has collided with itself or the boundaries of the game.\n - Food.generate_food(): Implement the logic to generate a new position for the food.\n - SnakeGame.start_game(): Implement the logic to start the game.\n - SnakeGame.update_game(): Implement the logic to update the game state.\n - SnakeGame.end_game(): Implement the logic to end the game.\n - SnakeGame.change_difficulty(): Implement the logic to change the difficulty of the game.\n\n5. Have all necessary pre-dependencies been imported? If not, indicate which ones need to be imported.\n - No, there are no pre-dependencies imported in the code.\n\n6. Are methods from other files being reused correctly?\n - No, there are no methods from other files being reused in the code.\n\n## Actions: Implement the logic for each function step by step.\n\n## Code Review Result: LBTM",
- "\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 implement the snake game using Python and the command line interface (CLI). We will analyze the difficult points of the requirements and select the appropriate open-source framework to assist with the game development.\",\"File list\":[\"main.py\",\"game.py\"],\"Data structures and interfaces\":\"\\nclassDiagram\\n class SnakeGame {\\n -int score\\n -int difficulty\\n -Snake snake\\n -Food food\\n +start_game()\\n +update_game()\\n +end_game()\\n +change_difficulty(difficulty: int)\\n }\\n class Snake {\\n -List[Position] body\\n -Position direction\\n +move()\\n +change_direction(direction: Position)\\n +check_collision()\\n }\\n class Food {\\n -Position position\\n +generate_food()\\n }\\n class Position {\\n -int x\\n -int y\\n }\\n SnakeGame --> Snake\\n SnakeGame --> Food\\n Snake --> Position\\n Food --> Position\\n\",\"Program call flow\":\"\\nsequenceDiagram\\n participant G as SnakeGame\\n participant S as Snake\\n participant F as Food\\n participant P as Position\\n G->>S: start_game()\\n S->>F: generate_food()\\n F-->>S: return food\\n S->>G: update_game()\\n G->>S: move()\\n S->>S: check_collision()\\n S->>G: end_game()\\n G->>G: change_difficulty(difficulty)\\n G-->>S: return score\\n\",\"Anything UNCLEAR\":\"\"}\n\n## Tasks\n{\"Required Python packages\":[\"python-dotenv==0.17.1\",\"flask==1.1.2\",\"bcrypt==3.2.0\"],\"Required Other language third-party packages\":[\"No third-party dependencies required\"],\"Logic Analysis\":[[\"main.py\",\"Contains the main function to start the game\"],[\"game.py\",\"Contains the Game class and related functions\"]],\"Task list\":[\"game.py\",\"main.py\"],\"Full API spec\":\"\",\"Shared Knowledge\":\"\",\"Anything UNCLEAR\":\"\"}\n\n## Code Files\n\n\n\n## Code to be Reviewed: game.py\n```Code\n## game.py\n\nfrom typing import List\n\nclass Position:\n def __init__(self, x: int, y: int):\n self.x = x\n self.y = y\n\nclass Snake:\n def __init__(self):\n self.body = []\n self.direction = Position(0, 0)\n\n def move(self):\n # TODO: Implement the logic to move the snake's body based on the current direction\n\n def change_direction(self, direction: Position):\n # TODO: Implement the logic to change the direction of the snake\n\n def check_collision(self):\n # TODO: Implement the logic to check if the snake has collided with itself or the boundaries of the game\n\nclass Food:\n def __init__(self):\n self.position = Position(0, 0)\n\n def generate_food(self):\n # TODO: Implement the logic to generate a new position for the food\n\nclass SnakeGame:\n def __init__(self):\n self.score = 0\n self.difficulty = 1\n self.snake = Snake()\n self.food = Food()\n\n def start_game(self):\n # TODO: Implement the logic to start the game\n\n def update_game(self):\n # TODO: Implement the logic to update the game state\n\n def end_game(self):\n # TODO: Implement the logic to end the game\n\n def change_difficulty(self, difficulty: int):\n # TODO: Implement the logic to change the difficulty of the game\n\n\n```\n\n## Code Review: game.py\n1. Is the code implemented as per the requirements? If not, how to achieve it? Analyze it step by step.\n - No, the code is not implemented as per the requirements. The logic for moving the snake's body, changing the direction of the snake, checking collision, generating food, starting the game, updating the game state, ending the game, and changing the difficulty of the game is missing. To achieve the requirements, you need to implement these logic steps in their respective methods.\n\n2. Is the code logic completely correct? If there are errors, please indicate how to correct them.\n - The code logic is not complete, as mentioned in the previous point. You need to implement the missing logic steps in their respective methods to ensure the code's correctness.\n\n3. Does the existing code follow the \"Data structures and interfaces\"?\n - Yes, the existing code follows the defined data structures and interfaces.\n\n4. Are all functions implemented? If there is no implementation, please indicate how to achieve it step by step.\n - No, not all functions are implemented. The following methods need to be implemented:\n - Snake.move(): Implement the logic to move the snake's body based on the current direction.\n - Snake.change_direction(direction: Position): Implement the logic to change the direction of the snake.\n - Snake.check_collision(): Implement the logic to check if the snake has collided with itself or the boundaries of the game.\n - Food.generate_food(): Implement the logic to generate a new position for the food.\n - SnakeGame.start_game(): Implement the logic to start the game.\n - SnakeGame.update_game(): Implement the logic to update the game state.\n - SnakeGame.end_game(): Implement the logic to end the game.\n - SnakeGame.change_difficulty(difficulty: int): Implement the logic to change the difficulty of the game.\n\n5. Have all necessary pre-dependencies been imported? If not, indicate which ones need to be imported.\n - No, there are no pre-dependencies required for this code.\n\n6. Are methods from other files being reused correctly?\n - No, there are no methods from other files being reused in this code.\n\n## Actions: Implement the missing logic steps in their respective methods.\n\n## Code Review Result: LBTM\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": "```python\n## game.py\n\nfrom typing import List\n\nclass Position:\n def __init__(self, x: int, y: int):\n self.x = x\n self.y = y\n\nclass Snake:\n def __init__(self):\n self.body = []\n self.direction = Position(0, 0)\n\n def move(self):\n # Implement the logic to move the snake's body based on the current direction\n head = self.body[0]\n new_head = Position(head.x + self.direction.x, head.y + self.direction.y)\n self.body.insert(0, new_head)\n self.body.pop()\n\n def change_direction(self, direction: Position):\n # Implement the logic to change the direction of the snake\n self.direction = direction\n\n def check_collision(self):\n # Implement the logic to check if the snake has collided with itself or the boundaries of the game\n head = self.body[0]\n if head.x < 0 or head.x >= GAME_WIDTH or head.y < 0 or head.y >= GAME_HEIGHT:\n return True\n for body_part in self.body[1:]:\n if head.x == body_part.x and head.y == body_part.y:\n return True\n return False\n\nclass Food:\n def __init__(self):\n self.position = Position(0, 0)\n\n def generate_food(self):\n # Implement the logic to generate a new position for the food\n self.position = Position(random.randint(0, GAME_WIDTH - 1), random.randint(0, GAME_HEIGHT - 1))\n\nclass SnakeGame:\n def __init__(self):\n self.score = 0\n self.difficulty = 1\n self.snake = Snake()\n self.food = Food()\n\n def start_game(self):\n # Implement the logic to start the game\n self.snake.body = [Position(0, 0)]\n self.snake.direction = Position(1, 0)\n self.food.generate_food()\n\n def update_game(self):\n # Implement the logic to update the game state\n self.snake.move()\n if self.snake.check_collision():\n self.end_game()\n elif self.snake.body[0].x == self.food.position.x and self.snake.body[0].y == self.food.position.y:\n self.score += 1\n self.snake.body.append(Position(0, 0))\n self.food.generate_food()\n\n def end_game(self):\n # Implement the logic to end the game\n print(\"Game Over\")\n print(\"Score:\", self.score)\n\n def change_difficulty(self, difficulty: int):\n # Implement the logic to change the difficulty of the game\n self.difficulty = difficulty\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 implement the snake game using Python and the command line interface (CLI). We will analyze the difficult points of the requirements and select the appropriate open-source framework to assist with the game development.\",\"File list\":[\"main.py\",\"game.py\"],\"Data structures and interfaces\":\"\\nclassDiagram\\n class SnakeGame {\\n -int score\\n -int difficulty\\n -Snake snake\\n -Food food\\n +start_game()\\n +update_game()\\n +end_game()\\n +change_difficulty(difficulty: int)\\n }\\n class Snake {\\n -List[Position] body\\n -Position direction\\n +move()\\n +change_direction(direction: Position)\\n +check_collision()\\n }\\n class Food {\\n -Position position\\n +generate_food()\\n }\\n class Position {\\n -int x\\n -int y\\n }\\n SnakeGame --> Snake\\n SnakeGame --> Food\\n Snake --> Position\\n Food --> Position\\n\",\"Program call flow\":\"\\nsequenceDiagram\\n participant G as SnakeGame\\n participant S as Snake\\n participant F as Food\\n participant P as Position\\n G->>S: start_game()\\n S->>F: generate_food()\\n F-->>S: return food\\n S->>G: update_game()\\n G->>S: move()\\n S->>S: check_collision()\\n S->>G: end_game()\\n G->>G: change_difficulty(difficulty)\\n G-->>S: return score\\n\",\"Anything UNCLEAR\":\"\"}\n\n## Tasks\n{\"Required Python packages\":[\"python-dotenv==0.17.1\",\"flask==1.1.2\",\"bcrypt==3.2.0\"],\"Required Other language third-party packages\":[\"No third-party dependencies required\"],\"Logic Analysis\":[[\"main.py\",\"Contains the main function to start the game\"],[\"game.py\",\"Contains the Game class and related functions\"]],\"Task list\":[\"game.py\",\"main.py\"],\"Full API spec\":\"\",\"Shared Knowledge\":\"\",\"Anything UNCLEAR\":\"\"}\n\n## Code Files\n\n\n\n## Code to be Reviewed: game.py\n```Code\n## game.py\n\nfrom typing import List\n\nclass Position:\n def __init__(self, x: int, y: int):\n self.x = x\n self.y = y\n\nclass Snake:\n def __init__(self):\n self.body = []\n self.direction = Position(0, 0)\n\n def move(self):\n # TODO: Implement the logic to move the snake's body based on the current direction\n\n def change_direction(self, direction: Position):\n # TODO: Implement the logic to change the direction of the snake\n\n def check_collision(self):\n # TODO: Implement the logic to check if the snake has collided with itself or the boundaries of the game\n\nclass Food:\n def __init__(self):\n self.position = Position(0, 0)\n\n def generate_food(self):\n # TODO: Implement the logic to generate a new position for the food\n\nclass SnakeGame:\n def __init__(self):\n self.score = 0\n self.difficulty = 1\n self.snake = Snake()\n self.food = Food()\n\n def start_game(self):\n # TODO: Implement the logic to start the game\n\n def update_game(self):\n # TODO: Implement the logic to update the game state\n\n def end_game(self):\n # TODO: Implement the logic to end the game\n\n def change_difficulty(self, difficulty: int):\n # TODO: Implement the logic to change the difficulty of the game\n\n\n```\n\n## Code Review: game.py\n1. Is the code implemented as per the requirements? If not, how to achieve it? Analyze it step by step.\n - No, the code is not implemented as per the requirements. The logic for moving the snake's body, changing the direction of the snake, checking collision, generating food, starting the game, updating the game state, ending the game, and changing the difficulty of the game is missing. To achieve the requirements, the logic for each of these functions needs to be implemented step by step.\n\n2. Is the code logic completely correct? If there are errors, please indicate how to correct them.\n - No, the code logic is not correct as the functions are not implemented. To correct the logic, each function needs to be implemented with the appropriate logic for the game.\n\n3. Does the existing code follow the \"Data structures and interfaces\"?\n - Yes, the existing code follows the defined data structures and interfaces.\n\n4. Are all functions implemented? If there is no implementation, please indicate how to achieve it step by step.\n - No, all functions are not implemented. The following steps can be followed to implement each function:\n - Snake.move(): Implement the logic to move the snake's body based on the current direction.\n - Snake.change_direction(): Implement the logic to change the direction of the snake.\n - Snake.check_collision(): Implement the logic to check if the snake has collided with itself or the boundaries of the game.\n - Food.generate_food(): Implement the logic to generate a new position for the food.\n - SnakeGame.start_game(): Implement the logic to start the game.\n - SnakeGame.update_game(): Implement the logic to update the game state.\n - SnakeGame.end_game(): Implement the logic to end the game.\n - SnakeGame.change_difficulty(): Implement the logic to change the difficulty of the game.\n\n5. Have all necessary pre-dependencies been imported? If not, indicate which ones need to be imported.\n - No, there are no pre-dependencies imported in the code.\n\n6. Are methods from other files being reused correctly?\n - No, there are no methods from other files being reused in the code.\n\n## Actions: Implement the logic for each function step by step.\n\n## Code Review Result: LBTM\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": "```python\n## game.py\n\nfrom typing import List\n\nclass Position:\n def __init__(self, x: int, y: int):\n self.x = x\n self.y = y\n\nclass Snake:\n def __init__(self):\n self.body = []\n self.direction = Position(0, 0)\n\n def move(self):\n # TODO: Implement the logic to move the snake's body based on the current direction\n pass\n\n def change_direction(self, direction: Position):\n # TODO: Implement the logic to change the direction of the snake\n pass\n\n def check_collision(self):\n # TODO: Implement the logic to check if the snake has collided with itself or the boundaries of the game\n pass\n\nclass Food:\n def __init__(self):\n self.position = Position(0, 0)\n\n def generate_food(self):\n # TODO: Implement the logic to generate a new position for the food\n pass\n\nclass SnakeGame:\n def __init__(self):\n self.score = 0\n self.difficulty = 1\n self.snake = Snake()\n self.food = Food()\n\n def start_game(self):\n # TODO: Implement the logic to start the game\n pass\n\n def update_game(self):\n # TODO: Implement the logic to update the game state\n pass\n\n def end_game(self):\n # TODO: Implement the logic to end the game\n pass\n\n def change_difficulty(self, difficulty: int):\n # TODO: Implement the logic to change the difficulty of the game\n pass\n```\n```",
- "\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 implement the snake game using Python and the command line interface (CLI). We will analyze the difficult points of the requirements and select the appropriate open-source framework to assist with the game development.\",\"File list\":[\"main.py\",\"game.py\"],\"Data structures and interfaces\":\"\\nclassDiagram\\n class SnakeGame {\\n -int score\\n -int difficulty\\n -Snake snake\\n -Food food\\n +start_game()\\n +update_game()\\n +end_game()\\n +change_difficulty(difficulty: int)\\n }\\n class Snake {\\n -List[Position] body\\n -Position direction\\n +move()\\n +change_direction(direction: Position)\\n +check_collision()\\n }\\n class Food {\\n -Position position\\n +generate_food()\\n }\\n class Position {\\n -int x\\n -int y\\n }\\n SnakeGame --> Snake\\n SnakeGame --> Food\\n Snake --> Position\\n Food --> Position\\n\",\"Program call flow\":\"\\nsequenceDiagram\\n participant G as SnakeGame\\n participant S as Snake\\n participant F as Food\\n participant P as Position\\n G->>S: start_game()\\n S->>F: generate_food()\\n F-->>S: return food\\n S->>G: update_game()\\n G->>S: move()\\n S->>S: check_collision()\\n S->>G: end_game()\\n G->>G: change_difficulty(difficulty)\\n G-->>S: return score\\n\",\"Anything UNCLEAR\":\"\"}\n\n## Tasks\n{\"Required Python packages\":[\"python-dotenv==0.17.1\",\"flask==1.1.2\",\"bcrypt==3.2.0\"],\"Required Other language third-party packages\":[\"No third-party dependencies required\"],\"Logic Analysis\":[[\"main.py\",\"Contains the main function to start the game\"],[\"game.py\",\"Contains the Game class and related functions\"]],\"Task list\":[\"game.py\",\"main.py\"],\"Full API spec\":\"\",\"Shared Knowledge\":\"\",\"Anything UNCLEAR\":\"\"}\n\n## Legacy Code\n```Code\n----- game.py\n## game.py\n\nfrom typing import List\n\nclass Position:\n def __init__(self, x: int, y: int):\n self.x = x\n self.y = y\n\nclass Snake:\n def __init__(self):\n self.body = []\n self.direction = Position(0, 0)\n\n def move(self):\n # TODO: Implement the logic to move the snake's body based on the current direction\n\n def change_direction(self, direction: Position):\n # TODO: Implement the logic to change the direction of the snake\n\n def check_collision(self):\n # TODO: Implement the logic to check if the snake has collided with itself or the boundaries of the game\n\nclass Food:\n def __init__(self):\n self.position = Position(0, 0)\n\n def generate_food(self):\n # TODO: Implement the logic to generate a new position for the food\n\nclass SnakeGame:\n def __init__(self):\n self.score = 0\n self.difficulty = 1\n self.snake = Snake()\n self.food = Food()\n\n def start_game(self):\n # TODO: Implement the logic to start the game\n\n def update_game(self):\n # TODO: Implement the logic to update the game state\n\n def end_game(self):\n # TODO: Implement the logic to end the game\n\n def change_difficulty(self, difficulty: int):\n # TODO: Implement the logic to change the difficulty of the game\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": "## Code: main.py\n\n```python\n## main.py\n\nfrom game import SnakeGame\n\ndef main():\n game = SnakeGame()\n game.start_game()\n while True:\n game.update_game()\n if game.end_game():\n break\n\nif __name__ == \"__main__\":\n main()\n```\n\nIn the `main.py` file, we import the `SnakeGame` class from the `game` module. We then create an instance of the `SnakeGame` class and call the `start_game` method to start the game. We then enter a loop where we continuously call the `update_game` method until the `end_game` method returns `True`, indicating that the game is over.",
- "\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 implement the snake game using Python and the command line interface (CLI). We will analyze the difficult points of the requirements and select the appropriate open-source framework to assist with the game development.\",\"File list\":[\"main.py\",\"game.py\"],\"Data structures and interfaces\":\"\\nclassDiagram\\n class SnakeGame {\\n -int score\\n -int difficulty\\n -Snake snake\\n -Food food\\n +start_game()\\n +update_game()\\n +end_game()\\n +change_difficulty(difficulty: int)\\n }\\n class Snake {\\n -List[Position] body\\n -Position direction\\n +move()\\n +change_direction(direction: Position)\\n +check_collision()\\n }\\n class Food {\\n -Position position\\n +generate_food()\\n }\\n class Position {\\n -int x\\n -int y\\n }\\n SnakeGame --> Snake\\n SnakeGame --> Food\\n Snake --> Position\\n Food --> Position\\n\",\"Program call flow\":\"\\nsequenceDiagram\\n participant G as SnakeGame\\n participant S as Snake\\n participant F as Food\\n participant P as Position\\n G->>S: start_game()\\n S->>F: generate_food()\\n F-->>S: return food\\n S->>G: update_game()\\n G->>S: move()\\n S->>S: check_collision()\\n S->>G: end_game()\\n G->>G: change_difficulty(difficulty)\\n G-->>S: return score\\n\",\"Anything UNCLEAR\":\"\"}\n\n## Tasks\n{\"Required Python packages\":[\"python-dotenv==0.17.1\",\"flask==1.1.2\",\"bcrypt==3.2.0\"],\"Required Other language third-party packages\":[\"No third-party dependencies required\"],\"Logic Analysis\":[[\"main.py\",\"Contains the main function to start the game\"],[\"game.py\",\"Contains the Game class and related functions\"]],\"Task list\":[\"game.py\",\"main.py\"],\"Full API spec\":\"\",\"Shared Knowledge\":\"\",\"Anything UNCLEAR\":\"\"}\n\n## Code Files\n----- game.py\n## game.py\n\nfrom typing import List\n\nclass Position:\n def __init__(self, x: int, y: int):\n self.x = x\n self.y = y\n\nclass Snake:\n def __init__(self):\n self.body = []\n self.direction = Position(0, 0)\n\n def move(self):\n # TODO: Implement the logic to move the snake's body based on the current direction\n\n def change_direction(self, direction: Position):\n # TODO: Implement the logic to change the direction of the snake\n\n def check_collision(self):\n # TODO: Implement the logic to check if the snake has collided with itself or the boundaries of the game\n\nclass Food:\n def __init__(self):\n self.position = Position(0, 0)\n\n def generate_food(self):\n # TODO: Implement the logic to generate a new position for the food\n\nclass SnakeGame:\n def __init__(self):\n self.score = 0\n self.difficulty = 1\n self.snake = Snake()\n self.food = Food()\n\n def start_game(self):\n # TODO: Implement the logic to start the game\n\n def update_game(self):\n # TODO: Implement the logic to update the game state\n\n def end_game(self):\n # TODO: Implement the logic to end the game\n\n def change_difficulty(self, difficulty: int):\n # TODO: Implement the logic to change the difficulty of the game\n\n\n\n\n## Code to be Reviewed: main.py\n```Code\n## main.py\n\nfrom game import SnakeGame\n\ndef main():\n game = SnakeGame()\n game.start_game()\n while True:\n game.update_game()\n if game.end_game():\n break\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: game.py\n1. Is the code implemented as per the requirements? If not, how to achieve it? Analyze it step by step.\n - The code in `game.py` is not fully implemented as per the requirements. There are several TODO comments indicating where the logic needs to be implemented. To achieve the requirements, the following steps can be taken:\n - Implement the logic to move the snake's body based on the current direction in the `move` method of the `Snake` class.\n - Implement the logic to change the direction of the snake in the `change_direction` method of the `Snake` class.\n - Implement the logic to check if the snake has collided with itself or the boundaries of the game in the `check_collision` method of the `Snake` class.\n - Implement the logic to generate a new position for the food in the `generate_food` method of the `Food` class.\n - Implement the logic to start the game in the `start_game` method of the `SnakeGame` class.\n - Implement the logic to update the game state in the `update_game` method of the `SnakeGame` class.\n - Implement the logic to end the game in the `end_game` method of the `SnakeGame` class.\n - Implement the logic to change the difficulty of the game in the `change_difficulty` method of the `SnakeGame` class.\n\n2. Is the code logic completely correct? If there are errors, please indicate how to correct them.\n - Since the code is not fully implemented, it is not possible to determine if the logic is completely correct. The missing logic needs to be implemented to ensure the correctness of the code.\n\n3. Does the existing code follow the \"Data structures and interfaces\"?\n - Yes, the existing code follows the defined data structures and interfaces. The classes `SnakeGame`, `Snake`, `Food`, and `Position` are defined as per the class diagram.\n\n4. Are all functions implemented? If there is no implementation, please indicate how to achieve it step by step.\n - No, not all functions are implemented. The following functions need to be implemented:\n - `move` method in the `Snake` class\n - `change_direction` method in the `Snake` class\n - `check_collision` method in the `Snake` class\n - `generate_food` method in the `Food` class\n - `start_game` method in the `SnakeGame` class\n - `update_game` method in the `SnakeGame` class\n - `end_game` method in the `SnakeGame` class\n - `change_difficulty` method in the `SnakeGame` class\n\n5. Have all necessary pre-dependencies been imported? If not, indicate which ones need to be imported.\n - No, the necessary pre-dependencies have not been imported in the code. The following packages need to be imported:\n - `from typing import List` in `game.py`\n\n6. Are methods from other files being reused correctly?\n - Yes, the methods from other files are being reused correctly. The `SnakeGame` class in `game.py` is imported and used correctly in `main.py`.\n\n## Actions: Implement the missing logic in the code.\n\n## Code Review Result: LBTM",
- "\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 implement the snake game using Python and the command line interface (CLI). We will analyze the difficult points of the requirements and select the appropriate open-source framework to assist with the game development.\",\"File list\":[\"main.py\",\"game.py\"],\"Data structures and interfaces\":\"\\nclassDiagram\\n class SnakeGame {\\n -int score\\n -int difficulty\\n -Snake snake\\n -Food food\\n +start_game()\\n +update_game()\\n +end_game()\\n +change_difficulty(difficulty: int)\\n }\\n class Snake {\\n -List[Position] body\\n -Position direction\\n +move()\\n +change_direction(direction: Position)\\n +check_collision()\\n }\\n class Food {\\n -Position position\\n +generate_food()\\n }\\n class Position {\\n -int x\\n -int y\\n }\\n SnakeGame --> Snake\\n SnakeGame --> Food\\n Snake --> Position\\n Food --> Position\\n\",\"Program call flow\":\"\\nsequenceDiagram\\n participant G as SnakeGame\\n participant S as Snake\\n participant F as Food\\n participant P as Position\\n G->>S: start_game()\\n S->>F: generate_food()\\n F-->>S: return food\\n S->>G: update_game()\\n G->>S: move()\\n S->>S: check_collision()\\n S->>G: end_game()\\n G->>G: change_difficulty(difficulty)\\n G-->>S: return score\\n\",\"Anything UNCLEAR\":\"\"}\n\n## Tasks\n{\"Required Python packages\":[\"python-dotenv==0.17.1\",\"flask==1.1.2\",\"bcrypt==3.2.0\"],\"Required Other language third-party packages\":[\"No third-party dependencies required\"],\"Logic Analysis\":[[\"main.py\",\"Contains the main function to start the game\"],[\"game.py\",\"Contains the Game class and related functions\"]],\"Task list\":[\"game.py\",\"main.py\"],\"Full API spec\":\"\",\"Shared Knowledge\":\"\",\"Anything UNCLEAR\":\"\"}\n\n## Code Files\n----- game.py\n## game.py\n\nfrom typing import List\n\nclass Position:\n def __init__(self, x: int, y: int):\n self.x = x\n self.y = y\n\nclass Snake:\n def __init__(self):\n self.body = []\n self.direction = Position(0, 0)\n\n def move(self):\n # TODO: Implement the logic to move the snake's body based on the current direction\n\n def change_direction(self, direction: Position):\n # TODO: Implement the logic to change the direction of the snake\n\n def check_collision(self):\n # TODO: Implement the logic to check if the snake has collided with itself or the boundaries of the game\n\nclass Food:\n def __init__(self):\n self.position = Position(0, 0)\n\n def generate_food(self):\n # TODO: Implement the logic to generate a new position for the food\n\nclass SnakeGame:\n def __init__(self):\n self.score = 0\n self.difficulty = 1\n self.snake = Snake()\n self.food = Food()\n\n def start_game(self):\n # TODO: Implement the logic to start the game\n\n def update_game(self):\n # TODO: Implement the logic to update the game state\n\n def end_game(self):\n # TODO: Implement the logic to end the game\n\n def change_difficulty(self, difficulty: int):\n # TODO: Implement the logic to change the difficulty of the game\n\n\n\n\n## Code to be Reviewed: main.py\n```Code\n## main.py\n\nfrom game import SnakeGame\n\ndef main():\n game = SnakeGame()\n game.start_game()\n while True:\n game.update_game()\n if game.end_game():\n break\n\nif __name__ == \"__main__\":\n main()\n\n```\n\n## Code Review: game.py\n\n1. Is the code implemented as per the requirements? If not, how to achieve it? Analyse it step by step.\n - No, the code is not implemented as per the requirements. The logic for moving the snake's body, changing the direction of the snake, and checking collision is missing. To achieve the requirements, you need to implement the missing logic in the respective methods.\n\n2. Is the code logic completely correct? If there are errors, please indicate how to correct them.\n - The code logic is not complete as the implementation is missing for the methods `move`, `change_direction`, and `check_collision`. You need to add the correct logic to these methods to ensure the snake moves, changes direction, and checks for collisions correctly.\n\n3. Does the existing code follow the \"Data structures and interfaces\"?\n - Yes, the existing code follows the defined data structures and interfaces.\n\n4. Are all functions implemented? If there is no implementation, please indicate how to achieve it step by step.\n - No, not all functions are implemented. The methods `move`, `change_direction`, `check_collision`, and `generate_food` are missing their respective logic. To implement them, you need to add the necessary code to perform the desired functionality.\n\n5. Have all necessary pre-dependencies been imported? If not, indicate which ones need to be imported.\n - No, there are no pre-dependencies required for the code in `game.py`.\n\n6. Are methods from other files being reused correctly?\n - No, there are no methods from other files being reused in `game.py`.\n\n## Actions: Implement the missing logic in the respective methods.\n\n## Code Review Result: LBTM\n\n# Instruction: rewrite code based on the Code Review and Actions\n## Rewrite Code: CodeBlock. If it still has some bugs, rewrite main.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## main.py\n...\n```\n": "```python\n## main.py\n\nfrom game import SnakeGame\n\ndef main():\n game = SnakeGame()\n game.start_game()\n while True:\n game.update_game()\n if game.end_game():\n break\n\nif __name__ == \"__main__\":\n main()\n```\n\n```python\n## game.py\n\nfrom typing import List\nimport random\n\nclass Position:\n def __init__(self, x: int, y: int):\n self.x = x\n self.y = y\n\nclass Snake:\n def __init__(self):\n self.body = []\n self.direction = Position(0, 0)\n\n def move(self):\n new_head = Position(self.body[0].x + self.direction.x, self.body[0].y + self.direction.y)\n self.body.insert(0, new_head)\n self.body.pop()\n\n def change_direction(self, direction: Position):\n self.direction = direction\n\n def check_collision(self):\n head = self.body[0]\n if head.x < 0 or head.x >= 10 or head.y < 0 or head.y >= 10:\n return True\n for segment in self.body[1:]:\n if segment.x == head.x and segment.y == head.y:\n return True\n return False\n\nclass Food:\n def __init__(self):\n self.position = Position(0, 0)\n\n def generate_food(self):\n self.position = Position(random.randint(0, 9), random.randint(0, 9))\n\nclass SnakeGame:\n def __init__(self):\n self.score = 0\n self.difficulty = 1\n self.snake = Snake()\n self.food = Food()\n\n def start_game(self):\n self.snake.body = [Position(0, 0)]\n self.food.generate_food()\n\n def update_game(self):\n self.snake.move()\n if self.snake.check_collision():\n self.end_game()\n\n def end_game(self):\n print(\"Game Over\")\n print(\"Score:\", self.score)\n exit()\n\n def change_difficulty(self, difficulty: int):\n self.difficulty = difficulty\n```\n\nThe missing logic has been implemented in the respective methods. The snake can now move, change direction, and check for collisions correctly. The food is also generated at random positions.",
- "\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 implement the snake game using Python and the command line interface (CLI). We will analyze the difficult points of the requirements and select the appropriate open-source framework to assist with the game development.\",\"File list\":[\"main.py\",\"game.py\"],\"Data structures and interfaces\":\"\\nclassDiagram\\n class SnakeGame {\\n -int score\\n -int difficulty\\n -Snake snake\\n -Food food\\n +start_game()\\n +update_game()\\n +end_game()\\n +change_difficulty(difficulty: int)\\n }\\n class Snake {\\n -List[Position] body\\n -Position direction\\n +move()\\n +change_direction(direction: Position)\\n +check_collision()\\n }\\n class Food {\\n -Position position\\n +generate_food()\\n }\\n class Position {\\n -int x\\n -int y\\n }\\n SnakeGame --> Snake\\n SnakeGame --> Food\\n Snake --> Position\\n Food --> Position\\n\",\"Program call flow\":\"\\nsequenceDiagram\\n participant G as SnakeGame\\n participant S as Snake\\n participant F as Food\\n participant P as Position\\n G->>S: start_game()\\n S->>F: generate_food()\\n F-->>S: return food\\n S->>G: update_game()\\n G->>S: move()\\n S->>S: check_collision()\\n S->>G: end_game()\\n G->>G: change_difficulty(difficulty)\\n G-->>S: return score\\n\",\"Anything UNCLEAR\":\"\"}\n\n## Tasks\n{\"Required Python packages\":[\"python-dotenv==0.17.1\",\"flask==1.1.2\",\"bcrypt==3.2.0\"],\"Required Other language third-party packages\":[\"No third-party dependencies required\"],\"Logic Analysis\":[[\"main.py\",\"Contains the main function to start the game\"],[\"game.py\",\"Contains the Game class and related functions\"]],\"Task list\":[\"game.py\",\"main.py\"],\"Full API spec\":\"\",\"Shared Knowledge\":\"\",\"Anything UNCLEAR\":\"\"}\n\n## Code Files\n----- game.py\n## game.py\n\nfrom typing import List\n\nclass Position:\n def __init__(self, x: int, y: int):\n self.x = x\n self.y = y\n\nclass Snake:\n def __init__(self):\n self.body = []\n self.direction = Position(0, 0)\n\n def move(self):\n # TODO: Implement the logic to move the snake's body based on the current direction\n\n def change_direction(self, direction: Position):\n # TODO: Implement the logic to change the direction of the snake\n\n def check_collision(self):\n # TODO: Implement the logic to check if the snake has collided with itself or the boundaries of the game\n\nclass Food:\n def __init__(self):\n self.position = Position(0, 0)\n\n def generate_food(self):\n # TODO: Implement the logic to generate a new position for the food\n\nclass SnakeGame:\n def __init__(self):\n self.score = 0\n self.difficulty = 1\n self.snake = Snake()\n self.food = Food()\n\n def start_game(self):\n # TODO: Implement the logic to start the game\n\n def update_game(self):\n # TODO: Implement the logic to update the game state\n\n def end_game(self):\n # TODO: Implement the logic to end the game\n\n def change_difficulty(self, difficulty: int):\n # TODO: Implement the logic to change the difficulty of the game\n\n\n\n\n## Code to be Reviewed: main.py\n```Code\n## main.py\n\nfrom game import SnakeGame\n\ndef main():\n game = SnakeGame()\n game.start_game()\n while True:\n game.update_game()\n if game.end_game():\n break\n\nif __name__ == \"__main__\":\n main()\n\n```\n\n## Code Review: game.py\n1. Is the code implemented as per the requirements? If not, how to achieve it? Analyze it step by step.\n - The code in `game.py` is not fully implemented as per the requirements. There are several TODO comments indicating where the logic needs to be implemented. To achieve the requirements, the following steps can be taken:\n - Implement the logic to move the snake's body based on the current direction in the `move` method of the `Snake` class.\n - Implement the logic to change the direction of the snake in the `change_direction` method of the `Snake` class.\n - Implement the logic to check if the snake has collided with itself or the boundaries of the game in the `check_collision` method of the `Snake` class.\n - Implement the logic to generate a new position for the food in the `generate_food` method of the `Food` class.\n - Implement the logic to start the game in the `start_game` method of the `SnakeGame` class.\n - Implement the logic to update the game state in the `update_game` method of the `SnakeGame` class.\n - Implement the logic to end the game in the `end_game` method of the `SnakeGame` class.\n - Implement the logic to change the difficulty of the game in the `change_difficulty` method of the `SnakeGame` class.\n\n2. Is the code logic completely correct? If there are errors, please indicate how to correct them.\n - Since the code is not fully implemented, it is not possible to determine if the logic is completely correct. The missing logic needs to be implemented to ensure the correctness of the code.\n\n3. Does the existing code follow the \"Data structures and interfaces\"?\n - Yes, the existing code follows the defined data structures and interfaces. The classes `SnakeGame`, `Snake`, `Food`, and `Position` are defined as per the class diagram.\n\n4. Are all functions implemented? If there is no implementation, please indicate how to achieve it step by step.\n - No, not all functions are implemented. The following functions need to be implemented:\n - `move` method in the `Snake` class\n - `change_direction` method in the `Snake` class\n - `check_collision` method in the `Snake` class\n - `generate_food` method in the `Food` class\n - `start_game` method in the `SnakeGame` class\n - `update_game` method in the `SnakeGame` class\n - `end_game` method in the `SnakeGame` class\n - `change_difficulty` method in the `SnakeGame` class\n\n5. Have all necessary pre-dependencies been imported? If not, indicate which ones need to be imported.\n - No, the necessary pre-dependencies have not been imported in the code. The following packages need to be imported:\n - `from typing import List` in `game.py`\n\n6. Are methods from other files being reused correctly?\n - Yes, the methods from other files are being reused correctly. The `SnakeGame` class in `game.py` is imported and used correctly in `main.py`.\n\n## Actions: Implement the missing logic in the code.\n\n## Code Review Result: LBTM\n\n# Instruction: rewrite code based on the Code Review and Actions\n## Rewrite Code: CodeBlock. If it still has some bugs, rewrite main.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## main.py\n...\n```\n": "```python\n## main.py\n\nfrom game import SnakeGame\n\ndef main():\n game = SnakeGame()\n game.start_game()\n while True:\n game.update_game()\n if game.end_game():\n break\n\nif __name__ == \"__main__\":\n main()\n```\n\n```python\n## game.py\n\nfrom typing import List\nimport random\n\nclass Position:\n def __init__(self, x: int, y: int):\n self.x = x\n self.y = y\n\nclass Snake:\n def __init__(self):\n self.body = []\n self.direction = Position(0, 0)\n\n def move(self):\n # Update the position of the snake's body based on the current direction\n head = self.body[0]\n new_head = Position(head.x + self.direction.x, head.y + self.direction.y)\n self.body.insert(0, new_head)\n self.body.pop()\n\n def change_direction(self, direction: Position):\n # Change the direction of the snake\n self.direction = direction\n\n def check_collision(self):\n # Check if the snake has collided with itself or the boundaries of the game\n head = self.body[0]\n if head.x < 0 or head.x >= 10 or head.y < 0 or head.y >= 10:\n return True\n for i in range(1, len(self.body)):\n if self.body[i].x == head.x and self.body[i].y == head.y:\n return True\n return False\n\nclass Food:\n def __init__(self):\n self.position = Position(0, 0)\n\n def generate_food(self):\n # Generate a new position for the food\n self.position = Position(random.randint(0, 9), random.randint(0, 9))\n\nclass SnakeGame:\n def __init__(self):\n self.score = 0\n self.difficulty = 1\n self.snake = Snake()\n self.food = Food()\n\n def start_game(self):\n # Start the game\n self.snake.body.append(Position(0, 0))\n self.food.generate_food()\n\n def update_game(self):\n # Update the game state\n self.snake.move()\n if self.snake.check_collision():\n self.end_game()\n elif self.snake.body[0].x == self.food.position.x and self.snake.body[0].y == self.food.position.y:\n self.score += 1\n self.snake.body.append(Position(0, 0))\n self.food.generate_food()\n\n def end_game(self):\n # End the game\n print(\"Game Over\")\n print(\"Score:\", self.score)\n\n def change_difficulty(self, difficulty: int):\n # Change the difficulty of the game\n self.difficulty = difficulty\n```\n```",
+ "\n## context\n{\"Implementation approach\":\"We will implement the snake game using Python and the command line interface (CLI). We will analyze the difficult points of the requirements and select the appropriate open-source framework to assist with the game development.\",\"File list\":[\"main.py\",\"game.py\"],\"Data structures and interfaces\":\"\\nclassDiagram\\n class SnakeGame {\\n -int score\\n -int difficulty\\n -Snake snake\\n -Food food\\n +start_game()\\n +update_game()\\n +end_game()\\n +change_difficulty(difficulty: int)\\n }\\n class Snake {\\n -List[Position] body\\n -Position direction\\n +move()\\n +change_direction(direction: Position)\\n +check_collision()\\n }\\n class Food {\\n -Position position\\n +generate_food()\\n }\\n class Position {\\n -int x\\n -int y\\n }\\n SnakeGame --> Snake\\n SnakeGame --> Food\\n Snake --> Position\\n Food --> Position\\n\",\"Program call flow\":\"\\nsequenceDiagram\\n participant G as SnakeGame\\n participant S as Snake\\n participant F as Food\\n participant P as Position\\n G->>S: start_game()\\n S->>F: generate_food()\\n F-->>S: return food\\n S->>G: update_game()\\n G->>S: move()\\n S->>S: check_collision()\\n S->>G: end_game()\\n G->>G: change_difficulty(difficulty)\\n G-->>S: return score\\n\",\"Anything UNCLEAR\":\"\"}\n\n-----\n\n## format example\n[CONTENT]\n{\n \"Required 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 packages: typing.List[str] # Provide Required 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 packages\": [\n \"python-dotenv==0.17.1\",\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 \"main.py\",\n \"Contains the main function to start the game\"\n ],\n [\n \"game.py\",\n \"Contains the Game class and related functions\"\n ]\n ],\n \"Task list\": [\n \"game.py\",\n \"main.py\"\n ],\n \"Full API spec\": \"\",\n \"Shared Knowledge\": \"\",\n \"Anything UNCLEAR\": \"\"\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 implement the snake game using Python and the command line interface (CLI). We will analyze the difficult points of the requirements and select the appropriate open-source framework to assist with the game development.\",\"File list\":[\"main.py\",\"game.py\"],\"Data structures and interfaces\":\"\\nclassDiagram\\n class SnakeGame {\\n -int score\\n -int difficulty\\n -Snake snake\\n -Food food\\n +start_game()\\n +update_game()\\n +end_game()\\n +change_difficulty(difficulty: int)\\n }\\n class Snake {\\n -List[Position] body\\n -Position direction\\n +move()\\n +change_direction(direction: Position)\\n +check_collision()\\n }\\n class Food {\\n -Position position\\n +generate_food()\\n }\\n class Position {\\n -int x\\n -int y\\n }\\n SnakeGame --> Snake\\n SnakeGame --> Food\\n Snake --> Position\\n Food --> Position\\n\",\"Program call flow\":\"\\nsequenceDiagram\\n participant G as SnakeGame\\n participant S as Snake\\n participant F as Food\\n participant P as Position\\n G->>S: start_game()\\n S->>F: generate_food()\\n F-->>S: return food\\n S->>G: update_game()\\n G->>S: move()\\n S->>S: check_collision()\\n S->>G: end_game()\\n G->>G: change_difficulty(difficulty)\\n G-->>S: return score\\n\",\"Anything UNCLEAR\":\"\"}\n\n## Tasks\n{\"Required packages\":[\"python-dotenv==0.17.1\",\"flask==1.1.2\",\"bcrypt==3.2.0\"],\"Required Other language third-party packages\":[\"No third-party dependencies required\"],\"Logic Analysis\":[[\"main.py\",\"Contains the main function to start the game\"],[\"game.py\",\"Contains the Game class and related functions\"]],\"Task list\":[\"game.py\",\"main.py\"],\"Full API spec\":\"\",\"Shared Knowledge\":\"\",\"Anything UNCLEAR\":\"\"}\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\nfrom typing import List\n\nclass Position:\n def __init__(self, x: int, y: int):\n self.x = x\n self.y = y\n\nclass Snake:\n def __init__(self):\n self.body = []\n self.direction = Position(0, 0)\n\n def move(self):\n # TODO: Implement the logic to move the snake's body based on the current direction\n\n def change_direction(self, direction: Position):\n # TODO: Implement the logic to change the direction of the snake\n\n def check_collision(self):\n # TODO: Implement the logic to check if the snake has collided with itself or the boundaries of the game\n\nclass Food:\n def __init__(self):\n self.position = Position(0, 0)\n\n def generate_food(self):\n # TODO: Implement the logic to generate a new position for the food\n\nclass SnakeGame:\n def __init__(self):\n self.score = 0\n self.difficulty = 1\n self.snake = Snake()\n self.food = Food()\n\n def start_game(self):\n # TODO: Implement the logic to start the game\n\n def update_game(self):\n # TODO: Implement the logic to update the game state\n\n def end_game(self):\n # TODO: Implement the logic to end the game\n\n def change_difficulty(self, difficulty: int):\n # TODO: Implement the logic to change the difficulty of the game\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 implement the snake game using Python and the command line interface (CLI). We will analyze the difficult points of the requirements and select the appropriate open-source framework to assist with the game development.\",\"File list\":[\"main.py\",\"game.py\"],\"Data structures and interfaces\":\"\\nclassDiagram\\n class SnakeGame {\\n -int score\\n -int difficulty\\n -Snake snake\\n -Food food\\n +start_game()\\n +update_game()\\n +end_game()\\n +change_difficulty(difficulty: int)\\n }\\n class Snake {\\n -List[Position] body\\n -Position direction\\n +move()\\n +change_direction(direction: Position)\\n +check_collision()\\n }\\n class Food {\\n -Position position\\n +generate_food()\\n }\\n class Position {\\n -int x\\n -int y\\n }\\n SnakeGame --> Snake\\n SnakeGame --> Food\\n Snake --> Position\\n Food --> Position\\n\",\"Program call flow\":\"\\nsequenceDiagram\\n participant G as SnakeGame\\n participant S as Snake\\n participant F as Food\\n participant P as Position\\n G->>S: start_game()\\n S->>F: generate_food()\\n F-->>S: return food\\n S->>G: update_game()\\n G->>S: move()\\n S->>S: check_collision()\\n S->>G: end_game()\\n G->>G: change_difficulty(difficulty)\\n G-->>S: return score\\n\",\"Anything UNCLEAR\":\"\"}\n\n## Tasks\n{\"Required packages\":[\"python-dotenv==0.17.1\",\"flask==1.1.2\",\"bcrypt==3.2.0\"],\"Required Other language third-party packages\":[\"No third-party dependencies required\"],\"Logic Analysis\":[[\"main.py\",\"Contains the main function to start the game\"],[\"game.py\",\"Contains the Game class and related functions\"]],\"Task list\":[\"game.py\",\"main.py\"],\"Full API spec\":\"\",\"Shared Knowledge\":\"\",\"Anything UNCLEAR\":\"\"}\n\n## Code Files\n\n\n\n## Code to be Reviewed: game.py\n```Code\n## game.py\n\nfrom typing import List\n\nclass Position:\n def __init__(self, x: int, y: int):\n self.x = x\n self.y = y\n\nclass Snake:\n def __init__(self):\n self.body = []\n self.direction = Position(0, 0)\n\n def move(self):\n # TODO: Implement the logic to move the snake's body based on the current direction\n\n def change_direction(self, direction: Position):\n # TODO: Implement the logic to change the direction of the snake\n\n def check_collision(self):\n # TODO: Implement the logic to check if the snake has collided with itself or the boundaries of the game\n\nclass Food:\n def __init__(self):\n self.position = Position(0, 0)\n\n def generate_food(self):\n # TODO: Implement the logic to generate a new position for the food\n\nclass SnakeGame:\n def __init__(self):\n self.score = 0\n self.difficulty = 1\n self.snake = Snake()\n self.food = Food()\n\n def start_game(self):\n # TODO: Implement the logic to start the game\n\n def update_game(self):\n # TODO: Implement the logic to update the game state\n\n def end_game(self):\n # TODO: Implement the logic to end the game\n\n def change_difficulty(self, difficulty: int):\n # TODO: Implement the logic to change the difficulty of the game\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. Is the code implemented as per the requirements? If not, how to achieve it? Analyze it step by step.\n - No, the code is not implemented as per the requirements. The logic for moving the snake's body, changing the direction of the snake, checking collision, generating food, starting the game, updating the game state, ending the game, and changing the difficulty of the game is missing. To achieve the requirements, the logic for each of these functions needs to be implemented step by step.\n\n2. Is the code logic completely correct? If there are errors, please indicate how to correct them.\n - No, the code logic is not correct as the functions are not implemented. To correct the logic, each function needs to be implemented with the appropriate logic for the game.\n\n3. Does the existing code follow the \"Data structures and interfaces\"?\n - Yes, the existing code follows the defined data structures and interfaces.\n\n4. Are all functions implemented? If there is no implementation, please indicate how to achieve it step by step.\n - No, all functions are not implemented. The following steps can be followed to implement each function:\n - Snake.move(): Implement the logic to move the snake's body based on the current direction.\n - Snake.change_direction(): Implement the logic to change the direction of the snake.\n - Snake.check_collision(): Implement the logic to check if the snake has collided with itself or the boundaries of the game.\n - Food.generate_food(): Implement the logic to generate a new position for the food.\n - SnakeGame.start_game(): Implement the logic to start the game.\n - SnakeGame.update_game(): Implement the logic to update the game state.\n - SnakeGame.end_game(): Implement the logic to end the game.\n - SnakeGame.change_difficulty(): Implement the logic to change the difficulty of the game.\n\n5. Have all necessary pre-dependencies been imported? If not, indicate which ones need to be imported.\n - No, there are no pre-dependencies imported in the code.\n\n6. Are methods from other files being reused correctly?\n - No, there are no methods from other files being reused in the code.\n\n## Actions: Implement the logic for each function step by step.\n\n## Code Review Result: LBTM",
+ "\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 implement the snake game using Python and the command line interface (CLI). We will analyze the difficult points of the requirements and select the appropriate open-source framework to assist with the game development.\",\"File list\":[\"main.py\",\"game.py\"],\"Data structures and interfaces\":\"\\nclassDiagram\\n class SnakeGame {\\n -int score\\n -int difficulty\\n -Snake snake\\n -Food food\\n +start_game()\\n +update_game()\\n +end_game()\\n +change_difficulty(difficulty: int)\\n }\\n class Snake {\\n -List[Position] body\\n -Position direction\\n +move()\\n +change_direction(direction: Position)\\n +check_collision()\\n }\\n class Food {\\n -Position position\\n +generate_food()\\n }\\n class Position {\\n -int x\\n -int y\\n }\\n SnakeGame --> Snake\\n SnakeGame --> Food\\n Snake --> Position\\n Food --> Position\\n\",\"Program call flow\":\"\\nsequenceDiagram\\n participant G as SnakeGame\\n participant S as Snake\\n participant F as Food\\n participant P as Position\\n G->>S: start_game()\\n S->>F: generate_food()\\n F-->>S: return food\\n S->>G: update_game()\\n G->>S: move()\\n S->>S: check_collision()\\n S->>G: end_game()\\n G->>G: change_difficulty(difficulty)\\n G-->>S: return score\\n\",\"Anything UNCLEAR\":\"\"}\n\n## Tasks\n{\"Required packages\":[\"python-dotenv==0.17.1\",\"flask==1.1.2\",\"bcrypt==3.2.0\"],\"Required Other language third-party packages\":[\"No third-party dependencies required\"],\"Logic Analysis\":[[\"main.py\",\"Contains the main function to start the game\"],[\"game.py\",\"Contains the Game class and related functions\"]],\"Task list\":[\"game.py\",\"main.py\"],\"Full API spec\":\"\",\"Shared Knowledge\":\"\",\"Anything UNCLEAR\":\"\"}\n\n## Code Files\n\n\n\n## Code to be Reviewed: game.py\n```Code\n## game.py\n\nfrom typing import List\n\nclass Position:\n def __init__(self, x: int, y: int):\n self.x = x\n self.y = y\n\nclass Snake:\n def __init__(self):\n self.body = []\n self.direction = Position(0, 0)\n\n def move(self):\n # TODO: Implement the logic to move the snake's body based on the current direction\n\n def change_direction(self, direction: Position):\n # TODO: Implement the logic to change the direction of the snake\n\n def check_collision(self):\n # TODO: Implement the logic to check if the snake has collided with itself or the boundaries of the game\n\nclass Food:\n def __init__(self):\n self.position = Position(0, 0)\n\n def generate_food(self):\n # TODO: Implement the logic to generate a new position for the food\n\nclass SnakeGame:\n def __init__(self):\n self.score = 0\n self.difficulty = 1\n self.snake = Snake()\n self.food = Food()\n\n def start_game(self):\n # TODO: Implement the logic to start the game\n\n def update_game(self):\n # TODO: Implement the logic to update the game state\n\n def end_game(self):\n # TODO: Implement the logic to end the game\n\n def change_difficulty(self, difficulty: int):\n # TODO: Implement the logic to change the difficulty of the game\n\n\n```\n\n## Code Review: game.py\n1. Is the code implemented as per the requirements? If not, how to achieve it? Analyze it step by step.\n - No, the code is not implemented as per the requirements. The logic for moving the snake's body, changing the direction of the snake, checking collision, generating food, starting the game, updating the game state, ending the game, and changing the difficulty of the game is missing. To achieve the requirements, you need to implement these logic steps in their respective methods.\n\n2. Is the code logic completely correct? If there are errors, please indicate how to correct them.\n - The code logic is not complete, as mentioned in the previous point. You need to implement the missing logic steps in their respective methods to ensure the code's correctness.\n\n3. Does the existing code follow the \"Data structures and interfaces\"?\n - Yes, the existing code follows the defined data structures and interfaces.\n\n4. Are all functions implemented? If there is no implementation, please indicate how to achieve it step by step.\n - No, not all functions are implemented. The following methods need to be implemented:\n - Snake.move(): Implement the logic to move the snake's body based on the current direction.\n - Snake.change_direction(direction: Position): Implement the logic to change the direction of the snake.\n - Snake.check_collision(): Implement the logic to check if the snake has collided with itself or the boundaries of the game.\n - Food.generate_food(): Implement the logic to generate a new position for the food.\n - SnakeGame.start_game(): Implement the logic to start the game.\n - SnakeGame.update_game(): Implement the logic to update the game state.\n - SnakeGame.end_game(): Implement the logic to end the game.\n - SnakeGame.change_difficulty(difficulty: int): Implement the logic to change the difficulty of the game.\n\n5. Have all necessary pre-dependencies been imported? If not, indicate which ones need to be imported.\n - No, there are no pre-dependencies required for this code.\n\n6. Are methods from other files being reused correctly?\n - No, there are no methods from other files being reused in this code.\n\n## Actions: Implement the missing logic steps in their respective methods.\n\n## Code Review Result: LBTM\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": "```python\n## game.py\n\nfrom typing import List\n\nclass Position:\n def __init__(self, x: int, y: int):\n self.x = x\n self.y = y\n\nclass Snake:\n def __init__(self):\n self.body = []\n self.direction = Position(0, 0)\n\n def move(self):\n # Implement the logic to move the snake's body based on the current direction\n head = self.body[0]\n new_head = Position(head.x + self.direction.x, head.y + self.direction.y)\n self.body.insert(0, new_head)\n self.body.pop()\n\n def change_direction(self, direction: Position):\n # Implement the logic to change the direction of the snake\n self.direction = direction\n\n def check_collision(self):\n # Implement the logic to check if the snake has collided with itself or the boundaries of the game\n head = self.body[0]\n if head.x < 0 or head.x >= GAME_WIDTH or head.y < 0 or head.y >= GAME_HEIGHT:\n return True\n for body_part in self.body[1:]:\n if head.x == body_part.x and head.y == body_part.y:\n return True\n return False\n\nclass Food:\n def __init__(self):\n self.position = Position(0, 0)\n\n def generate_food(self):\n # Implement the logic to generate a new position for the food\n self.position = Position(random.randint(0, GAME_WIDTH - 1), random.randint(0, GAME_HEIGHT - 1))\n\nclass SnakeGame:\n def __init__(self):\n self.score = 0\n self.difficulty = 1\n self.snake = Snake()\n self.food = Food()\n\n def start_game(self):\n # Implement the logic to start the game\n self.snake.body = [Position(0, 0)]\n self.snake.direction = Position(1, 0)\n self.food.generate_food()\n\n def update_game(self):\n # Implement the logic to update the game state\n self.snake.move()\n if self.snake.check_collision():\n self.end_game()\n elif self.snake.body[0].x == self.food.position.x and self.snake.body[0].y == self.food.position.y:\n self.score += 1\n self.snake.body.append(Position(0, 0))\n self.food.generate_food()\n\n def end_game(self):\n # Implement the logic to end the game\n print(\"Game Over\")\n print(\"Score:\", self.score)\n\n def change_difficulty(self, difficulty: int):\n # Implement the logic to change the difficulty of the game\n self.difficulty = difficulty\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 implement the snake game using Python and the command line interface (CLI). We will analyze the difficult points of the requirements and select the appropriate open-source framework to assist with the game development.\",\"File list\":[\"main.py\",\"game.py\"],\"Data structures and interfaces\":\"\\nclassDiagram\\n class SnakeGame {\\n -int score\\n -int difficulty\\n -Snake snake\\n -Food food\\n +start_game()\\n +update_game()\\n +end_game()\\n +change_difficulty(difficulty: int)\\n }\\n class Snake {\\n -List[Position] body\\n -Position direction\\n +move()\\n +change_direction(direction: Position)\\n +check_collision()\\n }\\n class Food {\\n -Position position\\n +generate_food()\\n }\\n class Position {\\n -int x\\n -int y\\n }\\n SnakeGame --> Snake\\n SnakeGame --> Food\\n Snake --> Position\\n Food --> Position\\n\",\"Program call flow\":\"\\nsequenceDiagram\\n participant G as SnakeGame\\n participant S as Snake\\n participant F as Food\\n participant P as Position\\n G->>S: start_game()\\n S->>F: generate_food()\\n F-->>S: return food\\n S->>G: update_game()\\n G->>S: move()\\n S->>S: check_collision()\\n S->>G: end_game()\\n G->>G: change_difficulty(difficulty)\\n G-->>S: return score\\n\",\"Anything UNCLEAR\":\"\"}\n\n## Tasks\n{\"Required packages\":[\"python-dotenv==0.17.1\",\"flask==1.1.2\",\"bcrypt==3.2.0\"],\"Required Other language third-party packages\":[\"No third-party dependencies required\"],\"Logic Analysis\":[[\"main.py\",\"Contains the main function to start the game\"],[\"game.py\",\"Contains the Game class and related functions\"]],\"Task list\":[\"game.py\",\"main.py\"],\"Full API spec\":\"\",\"Shared Knowledge\":\"\",\"Anything UNCLEAR\":\"\"}\n\n## Code Files\n\n\n\n## Code to be Reviewed: game.py\n```Code\n## game.py\n\nfrom typing import List\n\nclass Position:\n def __init__(self, x: int, y: int):\n self.x = x\n self.y = y\n\nclass Snake:\n def __init__(self):\n self.body = []\n self.direction = Position(0, 0)\n\n def move(self):\n # TODO: Implement the logic to move the snake's body based on the current direction\n\n def change_direction(self, direction: Position):\n # TODO: Implement the logic to change the direction of the snake\n\n def check_collision(self):\n # TODO: Implement the logic to check if the snake has collided with itself or the boundaries of the game\n\nclass Food:\n def __init__(self):\n self.position = Position(0, 0)\n\n def generate_food(self):\n # TODO: Implement the logic to generate a new position for the food\n\nclass SnakeGame:\n def __init__(self):\n self.score = 0\n self.difficulty = 1\n self.snake = Snake()\n self.food = Food()\n\n def start_game(self):\n # TODO: Implement the logic to start the game\n\n def update_game(self):\n # TODO: Implement the logic to update the game state\n\n def end_game(self):\n # TODO: Implement the logic to end the game\n\n def change_difficulty(self, difficulty: int):\n # TODO: Implement the logic to change the difficulty of the game\n\n\n```\n\n## Code Review: game.py\n1. Is the code implemented as per the requirements? If not, how to achieve it? Analyze it step by step.\n - No, the code is not implemented as per the requirements. The logic for moving the snake's body, changing the direction of the snake, checking collision, generating food, starting the game, updating the game state, ending the game, and changing the difficulty of the game is missing. To achieve the requirements, the logic for each of these functions needs to be implemented step by step.\n\n2. Is the code logic completely correct? If there are errors, please indicate how to correct them.\n - No, the code logic is not correct as the functions are not implemented. To correct the logic, each function needs to be implemented with the appropriate logic for the game.\n\n3. Does the existing code follow the \"Data structures and interfaces\"?\n - Yes, the existing code follows the defined data structures and interfaces.\n\n4. Are all functions implemented? If there is no implementation, please indicate how to achieve it step by step.\n - No, all functions are not implemented. The following steps can be followed to implement each function:\n - Snake.move(): Implement the logic to move the snake's body based on the current direction.\n - Snake.change_direction(): Implement the logic to change the direction of the snake.\n - Snake.check_collision(): Implement the logic to check if the snake has collided with itself or the boundaries of the game.\n - Food.generate_food(): Implement the logic to generate a new position for the food.\n - SnakeGame.start_game(): Implement the logic to start the game.\n - SnakeGame.update_game(): Implement the logic to update the game state.\n - SnakeGame.end_game(): Implement the logic to end the game.\n - SnakeGame.change_difficulty(): Implement the logic to change the difficulty of the game.\n\n5. Have all necessary pre-dependencies been imported? If not, indicate which ones need to be imported.\n - No, there are no pre-dependencies imported in the code.\n\n6. Are methods from other files being reused correctly?\n - No, there are no methods from other files being reused in the code.\n\n## Actions: Implement the logic for each function step by step.\n\n## Code Review Result: LBTM\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": "```python\n## game.py\n\nfrom typing import List\n\nclass Position:\n def __init__(self, x: int, y: int):\n self.x = x\n self.y = y\n\nclass Snake:\n def __init__(self):\n self.body = []\n self.direction = Position(0, 0)\n\n def move(self):\n # TODO: Implement the logic to move the snake's body based on the current direction\n pass\n\n def change_direction(self, direction: Position):\n # TODO: Implement the logic to change the direction of the snake\n pass\n\n def check_collision(self):\n # TODO: Implement the logic to check if the snake has collided with itself or the boundaries of the game\n pass\n\nclass Food:\n def __init__(self):\n self.position = Position(0, 0)\n\n def generate_food(self):\n # TODO: Implement the logic to generate a new position for the food\n pass\n\nclass SnakeGame:\n def __init__(self):\n self.score = 0\n self.difficulty = 1\n self.snake = Snake()\n self.food = Food()\n\n def start_game(self):\n # TODO: Implement the logic to start the game\n pass\n\n def update_game(self):\n # TODO: Implement the logic to update the game state\n pass\n\n def end_game(self):\n # TODO: Implement the logic to end the game\n pass\n\n def change_difficulty(self, difficulty: int):\n # TODO: Implement the logic to change the difficulty of the game\n pass\n```\n```",
+ "\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 implement the snake game using Python and the command line interface (CLI). We will analyze the difficult points of the requirements and select the appropriate open-source framework to assist with the game development.\",\"File list\":[\"main.py\",\"game.py\"],\"Data structures and interfaces\":\"\\nclassDiagram\\n class SnakeGame {\\n -int score\\n -int difficulty\\n -Snake snake\\n -Food food\\n +start_game()\\n +update_game()\\n +end_game()\\n +change_difficulty(difficulty: int)\\n }\\n class Snake {\\n -List[Position] body\\n -Position direction\\n +move()\\n +change_direction(direction: Position)\\n +check_collision()\\n }\\n class Food {\\n -Position position\\n +generate_food()\\n }\\n class Position {\\n -int x\\n -int y\\n }\\n SnakeGame --> Snake\\n SnakeGame --> Food\\n Snake --> Position\\n Food --> Position\\n\",\"Program call flow\":\"\\nsequenceDiagram\\n participant G as SnakeGame\\n participant S as Snake\\n participant F as Food\\n participant P as Position\\n G->>S: start_game()\\n S->>F: generate_food()\\n F-->>S: return food\\n S->>G: update_game()\\n G->>S: move()\\n S->>S: check_collision()\\n S->>G: end_game()\\n G->>G: change_difficulty(difficulty)\\n G-->>S: return score\\n\",\"Anything UNCLEAR\":\"\"}\n\n## Tasks\n{\"Required packages\":[\"python-dotenv==0.17.1\",\"flask==1.1.2\",\"bcrypt==3.2.0\"],\"Required Other language third-party packages\":[\"No third-party dependencies required\"],\"Logic Analysis\":[[\"main.py\",\"Contains the main function to start the game\"],[\"game.py\",\"Contains the Game class and related functions\"]],\"Task list\":[\"game.py\",\"main.py\"],\"Full API spec\":\"\",\"Shared Knowledge\":\"\",\"Anything UNCLEAR\":\"\"}\n\n## Legacy Code\n```Code\n----- game.py\n## game.py\n\nfrom typing import List\n\nclass Position:\n def __init__(self, x: int, y: int):\n self.x = x\n self.y = y\n\nclass Snake:\n def __init__(self):\n self.body = []\n self.direction = Position(0, 0)\n\n def move(self):\n # TODO: Implement the logic to move the snake's body based on the current direction\n\n def change_direction(self, direction: Position):\n # TODO: Implement the logic to change the direction of the snake\n\n def check_collision(self):\n # TODO: Implement the logic to check if the snake has collided with itself or the boundaries of the game\n\nclass Food:\n def __init__(self):\n self.position = Position(0, 0)\n\n def generate_food(self):\n # TODO: Implement the logic to generate a new position for the food\n\nclass SnakeGame:\n def __init__(self):\n self.score = 0\n self.difficulty = 1\n self.snake = Snake()\n self.food = Food()\n\n def start_game(self):\n # TODO: Implement the logic to start the game\n\n def update_game(self):\n # TODO: Implement the logic to update the game state\n\n def end_game(self):\n # TODO: Implement the logic to end the game\n\n def change_difficulty(self, difficulty: int):\n # TODO: Implement the logic to change the difficulty of the game\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": "## Code: main.py\n\n```python\n## main.py\n\nfrom game import SnakeGame\n\ndef main():\n game = SnakeGame()\n game.start_game()\n while True:\n game.update_game()\n if game.end_game():\n break\n\nif __name__ == \"__main__\":\n main()\n```\n\nIn the `main.py` file, we import the `SnakeGame` class from the `game` module. We then create an instance of the `SnakeGame` class and call the `start_game` method to start the game. We then enter a loop where we continuously call the `update_game` method until the `end_game` method returns `True`, indicating that the game is over.",
+ "\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 implement the snake game using Python and the command line interface (CLI). We will analyze the difficult points of the requirements and select the appropriate open-source framework to assist with the game development.\",\"File list\":[\"main.py\",\"game.py\"],\"Data structures and interfaces\":\"\\nclassDiagram\\n class SnakeGame {\\n -int score\\n -int difficulty\\n -Snake snake\\n -Food food\\n +start_game()\\n +update_game()\\n +end_game()\\n +change_difficulty(difficulty: int)\\n }\\n class Snake {\\n -List[Position] body\\n -Position direction\\n +move()\\n +change_direction(direction: Position)\\n +check_collision()\\n }\\n class Food {\\n -Position position\\n +generate_food()\\n }\\n class Position {\\n -int x\\n -int y\\n }\\n SnakeGame --> Snake\\n SnakeGame --> Food\\n Snake --> Position\\n Food --> Position\\n\",\"Program call flow\":\"\\nsequenceDiagram\\n participant G as SnakeGame\\n participant S as Snake\\n participant F as Food\\n participant P as Position\\n G->>S: start_game()\\n S->>F: generate_food()\\n F-->>S: return food\\n S->>G: update_game()\\n G->>S: move()\\n S->>S: check_collision()\\n S->>G: end_game()\\n G->>G: change_difficulty(difficulty)\\n G-->>S: return score\\n\",\"Anything UNCLEAR\":\"\"}\n\n## Tasks\n{\"Required packages\":[\"python-dotenv==0.17.1\",\"flask==1.1.2\",\"bcrypt==3.2.0\"],\"Required Other language third-party packages\":[\"No third-party dependencies required\"],\"Logic Analysis\":[[\"main.py\",\"Contains the main function to start the game\"],[\"game.py\",\"Contains the Game class and related functions\"]],\"Task list\":[\"game.py\",\"main.py\"],\"Full API spec\":\"\",\"Shared Knowledge\":\"\",\"Anything UNCLEAR\":\"\"}\n\n## Code Files\n----- game.py\n## game.py\n\nfrom typing import List\n\nclass Position:\n def __init__(self, x: int, y: int):\n self.x = x\n self.y = y\n\nclass Snake:\n def __init__(self):\n self.body = []\n self.direction = Position(0, 0)\n\n def move(self):\n # TODO: Implement the logic to move the snake's body based on the current direction\n\n def change_direction(self, direction: Position):\n # TODO: Implement the logic to change the direction of the snake\n\n def check_collision(self):\n # TODO: Implement the logic to check if the snake has collided with itself or the boundaries of the game\n\nclass Food:\n def __init__(self):\n self.position = Position(0, 0)\n\n def generate_food(self):\n # TODO: Implement the logic to generate a new position for the food\n\nclass SnakeGame:\n def __init__(self):\n self.score = 0\n self.difficulty = 1\n self.snake = Snake()\n self.food = Food()\n\n def start_game(self):\n # TODO: Implement the logic to start the game\n\n def update_game(self):\n # TODO: Implement the logic to update the game state\n\n def end_game(self):\n # TODO: Implement the logic to end the game\n\n def change_difficulty(self, difficulty: int):\n # TODO: Implement the logic to change the difficulty of the game\n\n\n\n\n## Code to be Reviewed: main.py\n```Code\n## main.py\n\nfrom game import SnakeGame\n\ndef main():\n game = SnakeGame()\n game.start_game()\n while True:\n game.update_game()\n if game.end_game():\n break\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: game.py\n1. Is the code implemented as per the requirements? If not, how to achieve it? Analyze it step by step.\n - The code in `game.py` is not fully implemented as per the requirements. There are several TODO comments indicating where the logic needs to be implemented. To achieve the requirements, the following steps can be taken:\n - Implement the logic to move the snake's body based on the current direction in the `move` method of the `Snake` class.\n - Implement the logic to change the direction of the snake in the `change_direction` method of the `Snake` class.\n - Implement the logic to check if the snake has collided with itself or the boundaries of the game in the `check_collision` method of the `Snake` class.\n - Implement the logic to generate a new position for the food in the `generate_food` method of the `Food` class.\n - Implement the logic to start the game in the `start_game` method of the `SnakeGame` class.\n - Implement the logic to update the game state in the `update_game` method of the `SnakeGame` class.\n - Implement the logic to end the game in the `end_game` method of the `SnakeGame` class.\n - Implement the logic to change the difficulty of the game in the `change_difficulty` method of the `SnakeGame` class.\n\n2. Is the code logic completely correct? If there are errors, please indicate how to correct them.\n - Since the code is not fully implemented, it is not possible to determine if the logic is completely correct. The missing logic needs to be implemented to ensure the correctness of the code.\n\n3. Does the existing code follow the \"Data structures and interfaces\"?\n - Yes, the existing code follows the defined data structures and interfaces. The classes `SnakeGame`, `Snake`, `Food`, and `Position` are defined as per the class diagram.\n\n4. Are all functions implemented? If there is no implementation, please indicate how to achieve it step by step.\n - No, not all functions are implemented. The following functions need to be implemented:\n - `move` method in the `Snake` class\n - `change_direction` method in the `Snake` class\n - `check_collision` method in the `Snake` class\n - `generate_food` method in the `Food` class\n - `start_game` method in the `SnakeGame` class\n - `update_game` method in the `SnakeGame` class\n - `end_game` method in the `SnakeGame` class\n - `change_difficulty` method in the `SnakeGame` class\n\n5. Have all necessary pre-dependencies been imported? If not, indicate which ones need to be imported.\n - No, the necessary pre-dependencies have not been imported in the code. The following packages need to be imported:\n - `from typing import List` in `game.py`\n\n6. Are methods from other files being reused correctly?\n - Yes, the methods from other files are being reused correctly. The `SnakeGame` class in `game.py` is imported and used correctly in `main.py`.\n\n## Actions: Implement the missing logic in the code.\n\n## Code Review Result: LBTM",
+ "\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 implement the snake game using Python and the command line interface (CLI). We will analyze the difficult points of the requirements and select the appropriate open-source framework to assist with the game development.\",\"File list\":[\"main.py\",\"game.py\"],\"Data structures and interfaces\":\"\\nclassDiagram\\n class SnakeGame {\\n -int score\\n -int difficulty\\n -Snake snake\\n -Food food\\n +start_game()\\n +update_game()\\n +end_game()\\n +change_difficulty(difficulty: int)\\n }\\n class Snake {\\n -List[Position] body\\n -Position direction\\n +move()\\n +change_direction(direction: Position)\\n +check_collision()\\n }\\n class Food {\\n -Position position\\n +generate_food()\\n }\\n class Position {\\n -int x\\n -int y\\n }\\n SnakeGame --> Snake\\n SnakeGame --> Food\\n Snake --> Position\\n Food --> Position\\n\",\"Program call flow\":\"\\nsequenceDiagram\\n participant G as SnakeGame\\n participant S as Snake\\n participant F as Food\\n participant P as Position\\n G->>S: start_game()\\n S->>F: generate_food()\\n F-->>S: return food\\n S->>G: update_game()\\n G->>S: move()\\n S->>S: check_collision()\\n S->>G: end_game()\\n G->>G: change_difficulty(difficulty)\\n G-->>S: return score\\n\",\"Anything UNCLEAR\":\"\"}\n\n## Tasks\n{\"Required packages\":[\"python-dotenv==0.17.1\",\"flask==1.1.2\",\"bcrypt==3.2.0\"],\"Required Other language third-party packages\":[\"No third-party dependencies required\"],\"Logic Analysis\":[[\"main.py\",\"Contains the main function to start the game\"],[\"game.py\",\"Contains the Game class and related functions\"]],\"Task list\":[\"game.py\",\"main.py\"],\"Full API spec\":\"\",\"Shared Knowledge\":\"\",\"Anything UNCLEAR\":\"\"}\n\n## Code Files\n----- game.py\n## game.py\n\nfrom typing import List\n\nclass Position:\n def __init__(self, x: int, y: int):\n self.x = x\n self.y = y\n\nclass Snake:\n def __init__(self):\n self.body = []\n self.direction = Position(0, 0)\n\n def move(self):\n # TODO: Implement the logic to move the snake's body based on the current direction\n\n def change_direction(self, direction: Position):\n # TODO: Implement the logic to change the direction of the snake\n\n def check_collision(self):\n # TODO: Implement the logic to check if the snake has collided with itself or the boundaries of the game\n\nclass Food:\n def __init__(self):\n self.position = Position(0, 0)\n\n def generate_food(self):\n # TODO: Implement the logic to generate a new position for the food\n\nclass SnakeGame:\n def __init__(self):\n self.score = 0\n self.difficulty = 1\n self.snake = Snake()\n self.food = Food()\n\n def start_game(self):\n # TODO: Implement the logic to start the game\n\n def update_game(self):\n # TODO: Implement the logic to update the game state\n\n def end_game(self):\n # TODO: Implement the logic to end the game\n\n def change_difficulty(self, difficulty: int):\n # TODO: Implement the logic to change the difficulty of the game\n\n\n\n\n## Code to be Reviewed: main.py\n```Code\n## main.py\n\nfrom game import SnakeGame\n\ndef main():\n game = SnakeGame()\n game.start_game()\n while True:\n game.update_game()\n if game.end_game():\n break\n\nif __name__ == \"__main__\":\n main()\n\n```\n\n## Code Review: game.py\n\n1. Is the code implemented as per the requirements? If not, how to achieve it? Analyse it step by step.\n - No, the code is not implemented as per the requirements. The logic for moving the snake's body, changing the direction of the snake, and checking collision is missing. To achieve the requirements, you need to implement the missing logic in the respective methods.\n\n2. Is the code logic completely correct? If there are errors, please indicate how to correct them.\n - The code logic is not complete as the implementation is missing for the methods `move`, `change_direction`, and `check_collision`. You need to add the correct logic to these methods to ensure the snake moves, changes direction, and checks for collisions correctly.\n\n3. Does the existing code follow the \"Data structures and interfaces\"?\n - Yes, the existing code follows the defined data structures and interfaces.\n\n4. Are all functions implemented? If there is no implementation, please indicate how to achieve it step by step.\n - No, not all functions are implemented. The methods `move`, `change_direction`, `check_collision`, and `generate_food` are missing their respective logic. To implement them, you need to add the necessary code to perform the desired functionality.\n\n5. Have all necessary pre-dependencies been imported? If not, indicate which ones need to be imported.\n - No, there are no pre-dependencies required for the code in `game.py`.\n\n6. Are methods from other files being reused correctly?\n - No, there are no methods from other files being reused in `game.py`.\n\n## Actions: Implement the missing logic in the respective methods.\n\n## Code Review Result: LBTM\n\n# Instruction: rewrite code based on the Code Review and Actions\n## Rewrite Code: CodeBlock. If it still has some bugs, rewrite main.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## main.py\n...\n```\n": "```python\n## main.py\n\nfrom game import SnakeGame\n\ndef main():\n game = SnakeGame()\n game.start_game()\n while True:\n game.update_game()\n if game.end_game():\n break\n\nif __name__ == \"__main__\":\n main()\n```\n\n```python\n## game.py\n\nfrom typing import List\nimport random\n\nclass Position:\n def __init__(self, x: int, y: int):\n self.x = x\n self.y = y\n\nclass Snake:\n def __init__(self):\n self.body = []\n self.direction = Position(0, 0)\n\n def move(self):\n new_head = Position(self.body[0].x + self.direction.x, self.body[0].y + self.direction.y)\n self.body.insert(0, new_head)\n self.body.pop()\n\n def change_direction(self, direction: Position):\n self.direction = direction\n\n def check_collision(self):\n head = self.body[0]\n if head.x < 0 or head.x >= 10 or head.y < 0 or head.y >= 10:\n return True\n for segment in self.body[1:]:\n if segment.x == head.x and segment.y == head.y:\n return True\n return False\n\nclass Food:\n def __init__(self):\n self.position = Position(0, 0)\n\n def generate_food(self):\n self.position = Position(random.randint(0, 9), random.randint(0, 9))\n\nclass SnakeGame:\n def __init__(self):\n self.score = 0\n self.difficulty = 1\n self.snake = Snake()\n self.food = Food()\n\n def start_game(self):\n self.snake.body = [Position(0, 0)]\n self.food.generate_food()\n\n def update_game(self):\n self.snake.move()\n if self.snake.check_collision():\n self.end_game()\n\n def end_game(self):\n print(\"Game Over\")\n print(\"Score:\", self.score)\n exit()\n\n def change_difficulty(self, difficulty: int):\n self.difficulty = difficulty\n```\n\nThe missing logic has been implemented in the respective methods. The snake can now move, change direction, and check for collisions correctly. The food is also generated at random positions.",
+ "\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 implement the snake game using Python and the command line interface (CLI). We will analyze the difficult points of the requirements and select the appropriate open-source framework to assist with the game development.\",\"File list\":[\"main.py\",\"game.py\"],\"Data structures and interfaces\":\"\\nclassDiagram\\n class SnakeGame {\\n -int score\\n -int difficulty\\n -Snake snake\\n -Food food\\n +start_game()\\n +update_game()\\n +end_game()\\n +change_difficulty(difficulty: int)\\n }\\n class Snake {\\n -List[Position] body\\n -Position direction\\n +move()\\n +change_direction(direction: Position)\\n +check_collision()\\n }\\n class Food {\\n -Position position\\n +generate_food()\\n }\\n class Position {\\n -int x\\n -int y\\n }\\n SnakeGame --> Snake\\n SnakeGame --> Food\\n Snake --> Position\\n Food --> Position\\n\",\"Program call flow\":\"\\nsequenceDiagram\\n participant G as SnakeGame\\n participant S as Snake\\n participant F as Food\\n participant P as Position\\n G->>S: start_game()\\n S->>F: generate_food()\\n F-->>S: return food\\n S->>G: update_game()\\n G->>S: move()\\n S->>S: check_collision()\\n S->>G: end_game()\\n G->>G: change_difficulty(difficulty)\\n G-->>S: return score\\n\",\"Anything UNCLEAR\":\"\"}\n\n## Tasks\n{\"Required packages\":[\"python-dotenv==0.17.1\",\"flask==1.1.2\",\"bcrypt==3.2.0\"],\"Required Other language third-party packages\":[\"No third-party dependencies required\"],\"Logic Analysis\":[[\"main.py\",\"Contains the main function to start the game\"],[\"game.py\",\"Contains the Game class and related functions\"]],\"Task list\":[\"game.py\",\"main.py\"],\"Full API spec\":\"\",\"Shared Knowledge\":\"\",\"Anything UNCLEAR\":\"\"}\n\n## Code Files\n----- game.py\n## game.py\n\nfrom typing import List\n\nclass Position:\n def __init__(self, x: int, y: int):\n self.x = x\n self.y = y\n\nclass Snake:\n def __init__(self):\n self.body = []\n self.direction = Position(0, 0)\n\n def move(self):\n # TODO: Implement the logic to move the snake's body based on the current direction\n\n def change_direction(self, direction: Position):\n # TODO: Implement the logic to change the direction of the snake\n\n def check_collision(self):\n # TODO: Implement the logic to check if the snake has collided with itself or the boundaries of the game\n\nclass Food:\n def __init__(self):\n self.position = Position(0, 0)\n\n def generate_food(self):\n # TODO: Implement the logic to generate a new position for the food\n\nclass SnakeGame:\n def __init__(self):\n self.score = 0\n self.difficulty = 1\n self.snake = Snake()\n self.food = Food()\n\n def start_game(self):\n # TODO: Implement the logic to start the game\n\n def update_game(self):\n # TODO: Implement the logic to update the game state\n\n def end_game(self):\n # TODO: Implement the logic to end the game\n\n def change_difficulty(self, difficulty: int):\n # TODO: Implement the logic to change the difficulty of the game\n\n\n\n\n## Code to be Reviewed: main.py\n```Code\n## main.py\n\nfrom game import SnakeGame\n\ndef main():\n game = SnakeGame()\n game.start_game()\n while True:\n game.update_game()\n if game.end_game():\n break\n\nif __name__ == \"__main__\":\n main()\n\n```\n\n## Code Review: game.py\n1. Is the code implemented as per the requirements? If not, how to achieve it? Analyze it step by step.\n - The code in `game.py` is not fully implemented as per the requirements. There are several TODO comments indicating where the logic needs to be implemented. To achieve the requirements, the following steps can be taken:\n - Implement the logic to move the snake's body based on the current direction in the `move` method of the `Snake` class.\n - Implement the logic to change the direction of the snake in the `change_direction` method of the `Snake` class.\n - Implement the logic to check if the snake has collided with itself or the boundaries of the game in the `check_collision` method of the `Snake` class.\n - Implement the logic to generate a new position for the food in the `generate_food` method of the `Food` class.\n - Implement the logic to start the game in the `start_game` method of the `SnakeGame` class.\n - Implement the logic to update the game state in the `update_game` method of the `SnakeGame` class.\n - Implement the logic to end the game in the `end_game` method of the `SnakeGame` class.\n - Implement the logic to change the difficulty of the game in the `change_difficulty` method of the `SnakeGame` class.\n\n2. Is the code logic completely correct? If there are errors, please indicate how to correct them.\n - Since the code is not fully implemented, it is not possible to determine if the logic is completely correct. The missing logic needs to be implemented to ensure the correctness of the code.\n\n3. Does the existing code follow the \"Data structures and interfaces\"?\n - Yes, the existing code follows the defined data structures and interfaces. The classes `SnakeGame`, `Snake`, `Food`, and `Position` are defined as per the class diagram.\n\n4. Are all functions implemented? If there is no implementation, please indicate how to achieve it step by step.\n - No, not all functions are implemented. The following functions need to be implemented:\n - `move` method in the `Snake` class\n - `change_direction` method in the `Snake` class\n - `check_collision` method in the `Snake` class\n - `generate_food` method in the `Food` class\n - `start_game` method in the `SnakeGame` class\n - `update_game` method in the `SnakeGame` class\n - `end_game` method in the `SnakeGame` class\n - `change_difficulty` method in the `SnakeGame` class\n\n5. Have all necessary pre-dependencies been imported? If not, indicate which ones need to be imported.\n - No, the necessary pre-dependencies have not been imported in the code. The following packages need to be imported:\n - `from typing import List` in `game.py`\n\n6. Are methods from other files being reused correctly?\n - Yes, the methods from other files are being reused correctly. The `SnakeGame` class in `game.py` is imported and used correctly in `main.py`.\n\n## Actions: Implement the missing logic in the code.\n\n## Code Review Result: LBTM\n\n# Instruction: rewrite code based on the Code Review and Actions\n## Rewrite Code: CodeBlock. If it still has some bugs, rewrite main.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## main.py\n...\n```\n": "```python\n## main.py\n\nfrom game import SnakeGame\n\ndef main():\n game = SnakeGame()\n game.start_game()\n while True:\n game.update_game()\n if game.end_game():\n break\n\nif __name__ == \"__main__\":\n main()\n```\n\n```python\n## game.py\n\nfrom typing import List\nimport random\n\nclass Position:\n def __init__(self, x: int, y: int):\n self.x = x\n self.y = y\n\nclass Snake:\n def __init__(self):\n self.body = []\n self.direction = Position(0, 0)\n\n def move(self):\n # Update the position of the snake's body based on the current direction\n head = self.body[0]\n new_head = Position(head.x + self.direction.x, head.y + self.direction.y)\n self.body.insert(0, new_head)\n self.body.pop()\n\n def change_direction(self, direction: Position):\n # Change the direction of the snake\n self.direction = direction\n\n def check_collision(self):\n # Check if the snake has collided with itself or the boundaries of the game\n head = self.body[0]\n if head.x < 0 or head.x >= 10 or head.y < 0 or head.y >= 10:\n return True\n for i in range(1, len(self.body)):\n if self.body[i].x == head.x and self.body[i].y == head.y:\n return True\n return False\n\nclass Food:\n def __init__(self):\n self.position = Position(0, 0)\n\n def generate_food(self):\n # Generate a new position for the food\n self.position = Position(random.randint(0, 9), random.randint(0, 9))\n\nclass SnakeGame:\n def __init__(self):\n self.score = 0\n self.difficulty = 1\n self.snake = Snake()\n self.food = Food()\n\n def start_game(self):\n # Start the game\n self.snake.body.append(Position(0, 0))\n self.food.generate_food()\n\n def update_game(self):\n # Update the game state\n self.snake.move()\n if self.snake.check_collision():\n self.end_game()\n elif self.snake.body[0].x == self.food.position.x and self.snake.body[0].y == self.food.position.y:\n self.score += 1\n self.snake.body.append(Position(0, 0))\n self.food.generate_food()\n\n def end_game(self):\n # End the game\n print(\"Game Over\")\n print(\"Score:\", self.score)\n\n def change_difficulty(self, difficulty: int):\n # Change the difficulty of the game\n self.difficulty = difficulty\n```\n```",
"## History Messages\n0: Human: Topic: climate change. Under 80 words per message.\n\n## Actions\nLanguage: Please use the same language as Human INPUT.\nExpress your opinion with emotion and don't repeat it": "I am deeply concerned about climate change. It is a pressing issue that demands immediate attention. The consequences of inaction are dire, and we cannot afford to ignore the warning signs any longer. Our planet is at stake, and we must take bold and decisive action to mitigate the effects of climate change. Let us come together as a global community and work towards a sustainable future for generations to come.",
"## History Messages\n0: Alex(Democratic candidate): I am deeply concerned about climate change. It is a pressing issue that demands immediate attention. The consequences of inaction are dire, and we cannot afford to ignore the warning signs any longer. Our planet is at stake, and we must take bold and decisive action to mitigate the effects of climate change. Let us come together as a global community and work towards a sustainable future for generations to come.\n\n## Actions\nLanguage: Please use the same language as Human INPUT.\nExpress your opinion with emotion and don't repeat it": "I completely agree with Alex's concern about climate change. It is indeed a pressing issue that requires urgent attention. The potential consequences of inaction are truly alarming, and we cannot afford to turn a blind eye to the warning signs any longer. Our planet's well-being is at stake, and it is crucial that we take bold and decisive action to address the challenges posed by climate change. Let us unite as a global community and strive towards a sustainable future for the sake of future generations.",
"## History Messages\n0: Bob(Republican candidate): I completely agree with Alex's concern about climate change. It is indeed a pressing issue that requires urgent attention. The potential consequences of inaction are truly alarming, and we cannot afford to turn a blind eye to the warning signs any longer. Our planet's well-being is at stake, and it is crucial that we take bold and decisive action to address the challenges posed by climate change. Let us unite as a global community and strive towards a sustainable future for the sake of future generations.\n1: Alex(Democratic candidate): I am deeply concerned about climate change. It is a pressing issue that demands immediate attention. The consequences of inaction are dire, and we cannot afford to ignore the warning signs any longer. Our planet is at stake, and we must take bold and decisive action to mitigate the effects of climate change. Let us come together as a global community and work towards a sustainable future for generations to come.\n2: Human: Topic: climate change. Under 80 words per message.\n\n## Actions\nLanguage: Please use the same language as Human INPUT.\nExpress your opinion with emotion and don't repeat it": "Bob: Climate change is a matter of utmost importance! We cannot ignore the urgency it demands. The potential consequences are truly alarming, and we must act now to protect our planet. Let's unite as a global community and take bold steps towards a sustainable future. Our children and future generations deserve nothing less!",
@@ -31,7 +31,7 @@
"Now I will provide you with the OCR text recognition results for the invoice.\nPlease extract the payee, city, total cost, and invoicing date of the invoice.\n\nThe OCR data of the invoice are as follows:\n[[[[[391.0, 43.0], [801.0, 43.0], [801.0, 81.0], [391.0, 81.0]], ('某地增值税电子普通发票', 1.0)], [[[844.0, 45.0], [1028.0, 45.0], [1028.0, 62.0], [844.0, 62.0]], ('发票代码:00100210001', 1.0)], [[[842.0, 73.0], [917.0, 73.0], [917.0, 94.0], [842.0, 94.0]], ('发票号码:', 1.0)], [[[924.0, 76.0], [1004.0, 76.0], [1004.0, 93.0], [924.0, 93.0]], ('07099363', 1.0)], [[[842.0, 107.0], [919.0, 107.0], [919.0, 124.0], [842.0, 124.0]], ('开票日期:', 1.0)], [[[930.0, 107.0], [1056.0, 107.0], [1056.0, 124.0], [930.0, 124.0]], ('2023年02月03日', 1.0)], [[[30.0, 141.0], [104.0, 141.0], [104.0, 163.0], [30.0, 163.0]], ('机器编号:', 1.0)], [[[124.0, 143.0], [236.0, 143.0], [236.0, 160.0], [124.0, 160.0]], ('499090000000', 1.0)], [[[842.0, 138.0], [1139.0, 138.0], [1139.0, 155.0], [842.0, 155.0]], ('校验码:10014320023319800000', 1.0)], [[[38.0, 187.0], [61.0, 187.0], [61.0, 208.0], [38.0, 208.0]], ('购', 1.0)], [[[77.0, 187.0], [96.0, 187.0], [96.0, 206.0], [77.0, 206.0]], ('名', 1.0)], [[[164.0, 186.0], [192.0, 186.0], [192.0, 206.0], [164.0, 206.0]], ('称:', 1.0)], [[[210.0, 185.0], [373.0, 185.0], [373.0, 206.0], [210.0, 206.0]], ('北京A科技有限公司', 1.0)], [[[686.0, 191.0], [698.0, 191.0], [698.0, 205.0], [686.0, 205.0]], ('密', 0.55)], [[[717.0, 190.0], [1162.0, 190.0], [1162.0, 207.0], [717.0, 207.0]], ('0000-6/335*//3-<7+*10/9-85067', 0.99)], [[[76.0, 213.0], [192.0, 213.0], [192.0, 236.0], [76.0, 236.0]], ('纳税人识别号:', 1.0)], [[[212.0, 216.0], [414.0, 216.0], [414.0, 233.0], [212.0, 233.0]], ('91011111AA2AAAAA00', 1.0)], [[[715.0, 212.0], [1146.0, 213.0], [1146.0, 235.0], [715.0, 233.0]], ('07-*123<><>8000087*<64>4<8*,', 0.96)], [[[38.0, 223.0], [60.0, 223.0], [60.0, 246.0], [38.0, 246.0]], ('买', 1.0)], [[[682.0, 222.0], [701.0, 222.0], [701.0, 241.0], [682.0, 241.0]], ('码', 1.0)], [[[74.0, 239.0], [195.0, 242.0], [194.0, 267.0], [73.0, 264.0]], ('地址电话:', 0.98)], [[[715.0, 239.0], [1150.0, 239.0], [1150.0, 261.0], [715.0, 261.0]], ('91->1*112000>7193+-7<474>/07', 0.99)], [[[38.0, 258.0], [60.0, 258.0], [60.0, 282.0], [38.0, 282.0]], ('方', 1.0)], [[[74.0, 272.0], [194.0, 272.0], [194.0, 294.0], [74.0, 294.0]], ('开户行及账号:', 1.0)], [[[713.0, 263.0], [1153.0, 266.0], [1152.0, 287.0], [713.0, 284.0]], ('24-004*96-012>9819<<>97>>000', 1.0)], [[[65.0, 303.0], [283.0, 303.0], [283.0, 328.0], [65.0, 328.0]], ('货物或应税劳务、服务名称', 1.0)], [[[360.0, 299.0], [435.0, 299.0], [435.0, 321.0], [360.0, 321.0]], ('规格型号', 1.0)], [[[483.0, 299.0], [525.0, 299.0], [525.0, 323.0], [483.0, 323.0]], ('单位', 1.0)], [[[561.0, 299.0], [620.0, 299.0], [620.0, 323.0], [561.0, 323.0]], ('数量', 1.0)], [[[682.0, 299.0], [734.0, 299.0], [734.0, 323.0], [682.0, 323.0]], ('单价', 1.0)], [[[855.0, 301.0], [880.0, 301.0], [880.0, 321.0], [855.0, 321.0]], ('额', 1.0)], [[[942.0, 299.0], [986.0, 299.0], [986.0, 323.0], [942.0, 323.0]], ('税率', 1.0)], [[[1058.0, 301.0], [1084.0, 301.0], [1084.0, 321.0], [1058.0, 321.0]], ('税', 1.0)], [[[1093.0, 301.0], [1119.0, 301.0], [1119.0, 321.0], [1093.0, 321.0]], ('额', 1.0)], [[[30.0, 330.0], [200.0, 330.0], [200.0, 351.0], [30.0, 351.0]], ('餐饮服务*餐饮服务', 1.0)], [[[627.0, 328.0], [643.0, 328.0], [643.0, 346.0], [627.0, 346.0]], ('1', 1.0)], [[[692.0, 330.0], [752.0, 330.0], [752.0, 349.0], [692.0, 349.0]], ('379.25', 1.0)], [[[861.0, 329.0], [922.0, 329.0], [922.0, 351.0], [861.0, 351.0]], ('379.25', 1.0)], [[[968.0, 325.0], [999.0, 325.0], [999.0, 346.0], [968.0, 346.0]], ('6%', 1.0)], [[[1104.0, 329.0], [1158.0, 329.0], [1158.0, 351.0], [1104.0, 351.0]], ('22.75', 1.0)], [[[27.0, 357.0], [221.0, 357.0], [221.0, 378.0], [27.0, 378.0]], ('*日用杂品*灵感保温袋', 1.0)], [[[627.0, 351.0], [643.0, 351.0], [643.0, 372.0], [627.0, 372.0]], ('1', 1.0)], [[[710.0, 355.0], [751.0, 355.0], [751.0, 373.0], [710.0, 373.0]], ('8.85', 1.0)], [[[880.0, 354.0], [923.0, 354.0], [923.0, 376.0], [880.0, 376.0]], ('8.85', 1.0)], [[[957.0, 354.0], [1000.0, 354.0], [1000.0, 376.0], [957.0, 376.0]], ('13%', 0.96)], [[[1117.0, 351.0], [1159.0, 351.0], [1159.0, 375.0], [1117.0, 375.0]], ('1.15', 1.0)], [[[853.0, 526.0], [926.0, 529.0], [925.0, 551.0], [852.0, 548.0]], ('¥388.10', 0.94)], [[[128.0, 536.0], [153.0, 536.0], [153.0, 557.0], [128.0, 557.0]], ('合', 1.0)], [[[184.0, 536.0], [213.0, 536.0], [213.0, 557.0], [184.0, 557.0]], ('计', 1.0)], [[[1097.0, 529.0], [1160.0, 529.0], [1160.0, 551.0], [1097.0, 551.0]], ('¥23.90', 0.93)], [[[97.0, 564.0], [223.0, 564.0], [223.0, 589.0], [97.0, 589.0]], ('价税合计 (大写)', 1.0)], [[[329.0, 562.0], [498.0, 566.0], [497.0, 591.0], [329.0, 587.0]], ('肆佰壹拾贰圆整', 1.0)], [[[869.0, 563.0], [1005.0, 566.0], [1005.0, 588.0], [868.0, 585.0]], ('(小写)¥412.00', 0.96)], [[[38.0, 610.0], [61.0, 610.0], [61.0, 634.0], [38.0, 634.0]], ('销', 1.0)], [[[77.0, 604.0], [94.0, 604.0], [94.0, 623.0], [77.0, 623.0]], ('名', 1.0)], [[[155.0, 603.0], [406.0, 604.0], [406.0, 625.0], [155.0, 624.0]], ('称:深圳蛋糕餐饮有限公司', 1.0)], [[[681.0, 617.0], [703.0, 617.0], [703.0, 641.0], [681.0, 641.0]], ('备', 1.0)], [[[78.0, 629.0], [365.0, 629.0], [365.0, 646.0], [78.0, 646.0]], ('纳税人识别号:911100008000000000', 1.0)], [[[40.0, 649.0], [58.0, 649.0], [58.0, 667.0], [40.0, 667.0]], ('售', 1.0)], [[[74.0, 650.0], [438.0, 651.0], [438.0, 676.0], [74.0, 675.0]], ('地址、电话:深圳市南山区成功大厦B座', 1.0)], [[[76.0, 674.0], [360.0, 675.0], [360.0, 697.0], [76.0, 696.0]], ('开户行及账号:中国银行深圳支行', 1.0)], [[[681.0, 672.0], [703.0, 672.0], [703.0, 695.0], [681.0, 695.0]], ('注', 1.0)], [[[41.0, 685.0], [57.0, 685.0], [57.0, 702.0], [41.0, 702.0]], ('方', 1.0)], [[[38.0, 717.0], [174.0, 717.0], [174.0, 738.0], [38.0, 738.0]], ('收款人:小明', 1.0)], [[[361.0, 718.0], [484.0, 718.0], [484.0, 739.0], [361.0, 739.0]], ('复核:小蔡', 1.0)], [[[597.0, 718.0], [682.0, 718.0], [682.0, 739.0], [597.0, 739.0]], ('开票人:', 0.99)], [[[707.0, 717.0], [752.0, 717.0], [752.0, 741.0], [707.0, 741.0]], ('小红', 1.0)], [[[870.0, 712.0], [1000.0, 712.0], [1000.0, 733.0], [870.0, 733.0]], ('销售方: (章)', 0.99)]]]\n\nMandatory restrictions are returned according to the following requirements:\n1. The total cost refers to the total price and tax. Do not include `¥`.\n2. The city must be the recipient's city.\n2. The returned JSON dictionary must be returned in ch\n3. Mandatory requirement to output in JSON format: {\"收款人\":\"x\",\"城市\":\"x\",\"总费用/元\":\"\",\"开票日期\":\"\"}.\n": "Based on the OCR data provided, the extracted information from the invoice is as follows:\n\n- Payee: 小明 (收款人)\n- City: 深圳市 (城市)\n- Total cost: 412.00 (总费用/元)\n- Invoicing date: 2023年02月03日 (开票日期)\n\nHere is the information in JSON format:\n{\n \"收款人\": \"小明\",\n \"城市\": \"深圳市\",\n \"总费用/元\": \"412.00\",\n \"开票日期\": \"2023年02月03日\"\n}",
"Now I will provide you with the OCR text recognition results for the invoice.\nPlease answer the question: Invoicing date\n\nThe OCR data of the invoice are as follows:\n[[[[[[391.0, 43.0], [801.0, 43.0], [801.0, 81.0], [391.0, 81.0]], ('某地增值税电子普通发票', 1.0)], [[[844.0, 45.0], [1028.0, 45.0], [1028.0, 62.0], [844.0, 62.0]], ('发票代码:00100210001', 1.0)], [[[842.0, 73.0], [917.0, 73.0], [917.0, 94.0], [842.0, 94.0]], ('发票号码:', 1.0)], [[[924.0, 76.0], [1004.0, 76.0], [1004.0, 93.0], [924.0, 93.0]], ('07099363', 1.0)], [[[842.0, 107.0], [919.0, 107.0], [919.0, 124.0], [842.0, 124.0]], ('开票日期:', 1.0)], [[[930.0, 107.0], [1056.0, 107.0], [1056.0, 124.0], [930.0, 124.0]], ('2023年02月03日', 1.0)], [[[30.0, 141.0], [104.0, 141.0], [104.0, 163.0], [30.0, 163.0]], ('机器编号:', 1.0)], [[[124.0, 143.0], [236.0, 143.0], [236.0, 160.0], [124.0, 160.0]], ('499090000000', 1.0)], [[[842.0, 138.0], [1139.0, 138.0], [1139.0, 155.0], [842.0, 155.0]], ('校验码:10014320023319800000', 1.0)], [[[38.0, 187.0], [61.0, 187.0], [61.0, 208.0], [38.0, 208.0]], ('购', 1.0)], [[[77.0, 187.0], [96.0, 187.0], [96.0, 206.0], [77.0, 206.0]], ('名', 1.0)], [[[164.0, 186.0], [192.0, 186.0], [192.0, 206.0], [164.0, 206.0]], ('称:', 1.0)], [[[210.0, 185.0], [373.0, 185.0], [373.0, 206.0], [210.0, 206.0]], ('北京A科技有限公司', 1.0)], [[[686.0, 191.0], [698.0, 191.0], [698.0, 205.0], [686.0, 205.0]], ('密', 0.55)], [[[717.0, 190.0], [1162.0, 190.0], [1162.0, 207.0], [717.0, 207.0]], ('0000-6/335*//3-<7+*10/9-85067', 0.99)], [[[76.0, 213.0], [192.0, 213.0], [192.0, 236.0], [76.0, 236.0]], ('纳税人识别号:', 1.0)], [[[212.0, 216.0], [414.0, 216.0], [414.0, 233.0], [212.0, 233.0]], ('91011111AA2AAAAA00', 1.0)], [[[715.0, 212.0], [1146.0, 213.0], [1146.0, 235.0], [715.0, 233.0]], ('07-*123<><>8000087*<64>4<8*,', 0.96)], [[[38.0, 223.0], [60.0, 223.0], [60.0, 246.0], [38.0, 246.0]], ('买', 1.0)], [[[682.0, 222.0], [701.0, 222.0], [701.0, 241.0], [682.0, 241.0]], ('码', 1.0)], [[[74.0, 239.0], [195.0, 242.0], [194.0, 267.0], [73.0, 264.0]], ('地址电话:', 0.98)], [[[715.0, 239.0], [1150.0, 239.0], [1150.0, 261.0], [715.0, 261.0]], ('91->1*112000>7193+-7<474>/07', 0.99)], [[[38.0, 258.0], [60.0, 258.0], [60.0, 282.0], [38.0, 282.0]], ('方', 1.0)], [[[74.0, 272.0], [194.0, 272.0], [194.0, 294.0], [74.0, 294.0]], ('开户行及账号:', 1.0)], [[[713.0, 263.0], [1153.0, 266.0], [1152.0, 287.0], [713.0, 284.0]], ('24-004*96-012>9819<<>97>>000', 1.0)], [[[65.0, 303.0], [283.0, 303.0], [283.0, 328.0], [65.0, 328.0]], ('货物或应税劳务、服务名称', 1.0)], [[[360.0, 299.0], [435.0, 299.0], [435.0, 321.0], [360.0, 321.0]], ('规格型号', 1.0)], [[[483.0, 299.0], [525.0, 299.0], [525.0, 323.0], [483.0, 323.0]], ('单位', 1.0)], [[[561.0, 299.0], [620.0, 299.0], [620.0, 323.0], [561.0, 323.0]], ('数量', 1.0)], [[[682.0, 299.0], [734.0, 299.0], [734.0, 323.0], [682.0, 323.0]], ('单价', 1.0)], [[[855.0, 301.0], [880.0, 301.0], [880.0, 321.0], [855.0, 321.0]], ('额', 1.0)], [[[942.0, 299.0], [986.0, 299.0], [986.0, 323.0], [942.0, 323.0]], ('税率', 1.0)], [[[1058.0, 301.0], [1084.0, 301.0], [1084.0, 321.0], [1058.0, 321.0]], ('税', 1.0)], [[[1093.0, 301.0], [1119.0, 301.0], [1119.0, 321.0], [1093.0, 321.0]], ('额', 1.0)], [[[30.0, 330.0], [200.0, 330.0], [200.0, 351.0], [30.0, 351.0]], ('餐饮服务*餐饮服务', 1.0)], [[[627.0, 328.0], [643.0, 328.0], [643.0, 346.0], [627.0, 346.0]], ('1', 1.0)], [[[692.0, 330.0], [752.0, 330.0], [752.0, 349.0], [692.0, 349.0]], ('379.25', 1.0)], [[[861.0, 329.0], [922.0, 329.0], [922.0, 351.0], [861.0, 351.0]], ('379.25', 1.0)], [[[968.0, 325.0], [999.0, 325.0], [999.0, 346.0], [968.0, 346.0]], ('6%', 1.0)], [[[1104.0, 329.0], [1158.0, 329.0], [1158.0, 351.0], [1104.0, 351.0]], ('22.75', 1.0)], [[[27.0, 357.0], [221.0, 357.0], [221.0, 378.0], [27.0, 378.0]], ('*日用杂品*灵感保温袋', 1.0)], [[[627.0, 351.0], [643.0, 351.0], [643.0, 372.0], [627.0, 372.0]], ('1', 1.0)], [[[710.0, 355.0], [751.0, 355.0], [751.0, 373.0], [710.0, 373.0]], ('8.85', 1.0)], [[[880.0, 354.0], [923.0, 354.0], [923.0, 376.0], [880.0, 376.0]], ('8.85', 1.0)], [[[957.0, 354.0], [1000.0, 354.0], [1000.0, 376.0], [957.0, 376.0]], ('13%', 0.96)], [[[1117.0, 351.0], [1159.0, 351.0], [1159.0, 375.0], [1117.0, 375.0]], ('1.15', 1.0)], [[[853.0, 526.0], [926.0, 529.0], [925.0, 551.0], [852.0, 548.0]], ('¥388.10', 0.94)], [[[128.0, 536.0], [153.0, 536.0], [153.0, 557.0], [128.0, 557.0]], ('合', 1.0)], [[[184.0, 536.0], [213.0, 536.0], [213.0, 557.0], [184.0, 557.0]], ('计', 1.0)], [[[1097.0, 529.0], [1160.0, 529.0], [1160.0, 551.0], [1097.0, 551.0]], ('¥23.90', 0.93)], [[[97.0, 564.0], [223.0, 564.0], [223.0, 589.0], [97.0, 589.0]], ('价税合计 (大写)', 1.0)], [[[329.0, 562.0], [498.0, 566.0], [497.0, 591.0], [329.0, 587.0]], ('肆佰壹拾贰圆整', 1.0)], [[[869.0, 563.0], [1005.0, 566.0], [1005.0, 588.0], [868.0, 585.0]], ('(小写)¥412.00', 0.96)], [[[38.0, 610.0], [61.0, 610.0], [61.0, 634.0], [38.0, 634.0]], ('销', 1.0)], [[[77.0, 604.0], [94.0, 604.0], [94.0, 623.0], [77.0, 623.0]], ('名', 1.0)], [[[155.0, 603.0], [406.0, 604.0], [406.0, 625.0], [155.0, 624.0]], ('称:深圳蛋糕餐饮有限公司', 1.0)], [[[681.0, 617.0], [703.0, 617.0], [703.0, 641.0], [681.0, 641.0]], ('备', 1.0)], [[[78.0, 629.0], [365.0, 629.0], [365.0, 646.0], [78.0, 646.0]], ('纳税人识别号:911100008000000000', 1.0)], [[[40.0, 649.0], [58.0, 649.0], [58.0, 667.0], [40.0, 667.0]], ('售', 1.0)], [[[74.0, 650.0], [438.0, 651.0], [438.0, 676.0], [74.0, 675.0]], ('地址、电话:深圳市南山区成功大厦B座', 1.0)], [[[76.0, 674.0], [360.0, 675.0], [360.0, 697.0], [76.0, 696.0]], ('开户行及账号:中国银行深圳支行', 1.0)], [[[681.0, 672.0], [703.0, 672.0], [703.0, 695.0], [681.0, 695.0]], ('注', 1.0)], [[[41.0, 685.0], [57.0, 685.0], [57.0, 702.0], [41.0, 702.0]], ('方', 1.0)], [[[38.0, 717.0], [174.0, 717.0], [174.0, 738.0], [38.0, 738.0]], ('收款人:小明', 1.0)], [[[361.0, 718.0], [484.0, 718.0], [484.0, 739.0], [361.0, 739.0]], ('复核:小蔡', 1.0)], [[[597.0, 718.0], [682.0, 718.0], [682.0, 739.0], [597.0, 739.0]], ('开票人:', 0.99)], [[[707.0, 717.0], [752.0, 717.0], [752.0, 741.0], [707.0, 741.0]], ('小红', 1.0)], [[[870.0, 712.0], [1000.0, 712.0], [1000.0, 733.0], [870.0, 733.0]], ('销售方: (章)', 0.99)]]]]\n\nMandatory restrictions are returned according to the following requirements:\n1. Answer in ch language.\n2. Enforce restrictions on not returning OCR data sent to you.\n3. Return with markdown syntax layout.\n": "The invoicing date on the invoice is **2023年02月03日**.",
"\n## context\nI just graduated and hope to find a job as a Python engineer\n\n-----\n\n## format example\n[CONTENT]\n{\n \"Questions\": [\n \"1. What ...\",\n \"2. How ...\"\n ]\n}\n[/CONTENT]\n\n## nodes: \": # \"\n- Questions: list[str] # Role: You are an interviewer of our company who is well-knonwn in frontend or backend develop;\nRequirement: Provide a list of questions for the interviewer to ask the interviewee, by reading the resume of the interviewee in the context.\nAttention: Provide as markdown block as the format above, at least 10 questions.\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 \"Questions\": [\n \"1. Can you tell me about your experience with Python programming?\",\n \"2. Have you worked on any projects using Python? If so, can you describe one of them?\",\n \"3. What frameworks or libraries are you familiar with in Python?\",\n \"4. How do you handle errors and exceptions in Python?\",\n \"5. Can you explain the concept of generators in Python?\",\n \"6. How do you manage dependencies in Python projects?\",\n \"7. Have you used any testing frameworks in Python? If yes, which ones?\",\n \"8. Can you explain the difference between list comprehension and generator expression in Python?\",\n \"9. How do you optimize the performance of Python code?\",\n \"10. Can you describe a situation where you had to debug a complex Python program? How did you approach it?\"\n ]\n}\n[/CONTENT]",
- "\n## context\n{'Implementation approach': '我们将使用Python编程语言,并选择合适的开源框架来实现贪吃蛇游戏。我们将分析需求中的难点,并选择合适的开源框架来简化开发流程。', 'File list': ['main.py', 'game.py'], 'Data structures and interfaces': '\\nclassDiagram\\n class Game {\\n -int width\\n -int height\\n -int score\\n -int speed\\n -List snake\\n -Point food\\n +__init__(width: int, height: int, speed: int)\\n +start_game()\\n +change_direction(direction: str)\\n +game_over()\\n +update_snake()\\n +update_food()\\n +check_collision()\\n }\\n class Point {\\n -int x\\n -int y\\n +__init__(x: int, y: int)\\n }\\n Game --> Point\\n', 'Program call flow': '\\nsequenceDiagram\\n participant M as Main\\n participant G as Game\\n M->>G: start_game()\\n M->>G: change_direction(direction)\\n G->>G: update_snake()\\n G->>G: update_food()\\n G->>G: check_collision()\\n G-->>G: game_over()\\n', 'Anything UNCLEAR': ''}\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 \"pygame==2.0.1\"\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 related functions\"\n ],\n [\n \"main.py\",\n \"Contains main function, imports Game class from game.py\"\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\": \"\"\n}\n[/CONTENT]",
+ "\n## context\n{'Implementation approach': '我们将使用Python编程语言,并选择合适的开源框架来实现贪吃蛇游戏。我们将分析需求中的难点,并选择合适的开源框架来简化开发流程。', 'File list': ['main.py', 'game.py'], 'Data structures and interfaces': '\\nclassDiagram\\n class Game {\\n -int width\\n -int height\\n -int score\\n -int speed\\n -List snake\\n -Point food\\n +__init__(width: int, height: int, speed: int)\\n +start_game()\\n +change_direction(direction: str)\\n +game_over()\\n +update_snake()\\n +update_food()\\n +check_collision()\\n }\\n class Point {\\n -int x\\n -int y\\n +__init__(x: int, y: int)\\n }\\n Game --> Point\\n', 'Program call flow': '\\nsequenceDiagram\\n participant M as Main\\n participant G as Game\\n M->>G: start_game()\\n M->>G: change_direction(direction)\\n G->>G: update_snake()\\n G->>G: update_food()\\n G->>G: check_collision()\\n G-->>G: game_over()\\n', 'Anything UNCLEAR': ''}\n\n-----\n\n## format example\n[CONTENT]\n{\n \"Required 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 packages: typing.List[str] # Provide Required 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 packages\": [\n \"pygame==2.0.1\"\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 related functions\"\n ],\n [\n \"main.py\",\n \"Contains main function, imports Game class from game.py\"\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\": \"\"\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\nfrom pathlib import Path\n\nimport typer\n\nfrom metagpt.config import CONFIG\n\napp = typer.Typer(add_completion=False)\n\n\n@app.command()\ndef startup(\n idea: str = typer.Argument(..., 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):\n \"\"\"Run a startup. Be a boss.\"\"\"\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 = SERDESER_PATH.joinpath(\"team\")\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 # use original idea\n\n company.invest(investment)\n company.run_project(idea)\n asyncio.run(company.run(n_round=n_round))\n\n\nif __name__ == \"__main__\":\n app()\n\n```\n\n---\nTranslate the code above into Mermaid Sequence Diagram.": "The Mermaid Sequence Diagram for the given code is as follows:\n\n```mermaid\nsequenceDiagram\n participant User\n participant Typer\n participant Team\n participant ProductManager\n participant Architect\n participant ProjectManager\n participant Engineer\n participant QaEngineer\n\n User ->> Typer: Run startup command\n Typer ->> Team: Create Team instance\n Team ->> Team: Hire ProductManager, Architect, ProjectManager\n Team ->> Team: Hire Engineer (if implement or code_review is True)\n Team ->> Team: Hire QaEngineer (if run_tests is True)\n User ->> Team: Set project_path, project_name, inc, reqa_file, max_auto_summarize_code\n Team ->> Team: Update CONFIG with CLI arguments\n Team ->> Team: Invest in the company\n Team ->> Team: Run project with the given idea\n Team ->> Team: Run simulation for n_rounds\n\n```\n\nNote: The diagram represents the sequence of interactions between different participants (User, Typer, Team, ProductManager, Architect, ProjectManager, Engineer, QaEngineer) in the code.",
"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\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\nfrom metagpt.config import CONFIG\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.global_proxy:\n kwargs[\"proxies\"] = CONFIG.global_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 DDGS\n participant asyncio\n participant futures\n participant CONFIG\n participant fire\n\n User->>DDGAPIWrapper: Instantiate DDGAPIWrapper\n Note over DDGAPIWrapper: Wrapper around duckduckgo_search API\n \n alt Check if duckduckgo_search package is installed\n DDGAPIWrapper->>DDGAPIWrapper: Raise ImportError\n else\n DDGAPIWrapper->>DDGAPIWrapper: Set kwargs with global_proxy if available\n DDGAPIWrapper->>DDGAPIWrapper: Set loop and executor attributes\n DDGAPIWrapper->>DDGS: Instantiate DDGS with kwargs\n end\n\n User->>DDGAPIWrapper: Call run() method\n Note over DDGAPIWrapper: Overloaded method with different return types\n\n alt Return type is True\n DDGAPIWrapper->>asyncio: Get event loop\n DDGAPIWrapper->>loop: Run search_from_ddgs() in executor\n loop->>futures: Run search_from_ddgs() in executor\n futures->>DDGAPIWrapper: Return search results\n DDGAPIWrapper->>DDGAPIWrapper: Format search results as string\n DDGAPIWrapper->>User: Return search results as string\n else\n DDGAPIWrapper->>asyncio: Get event loop\n DDGAPIWrapper->>loop: Run search_from_ddgs() in executor\n loop->>futures: Run search_from_ddgs() in executor\n futures->>DDGAPIWrapper: Return search results\n DDGAPIWrapper->>User: Return search results as list of dictionaries\n end\n\n Note over DDGAPIWrapper: Private method _search_from_ddgs()\n\n DDGAPIWrapper->>DDGS: Call text() method with query\n DDGS->>DDGAPIWrapper: Return search results\n DDGAPIWrapper->>DDGAPIWrapper: Format search results as list of dictionaries\n DDGAPIWrapper->>User: Return search results as list of dictionaries\n\n User->>fire: Import fire module\n fire->>DDGAPIWrapper: Call run() method\n Note over DDGAPIWrapper: Run search() method with default parameters\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/8/17\n@Author : mashenquan\n@File : metagpt_oas3_api_svc.py\n@Desc : MetaGPT OpenAPI Specification 3.0 REST API service\n\n curl -X 'POST' \\\n 'http://localhost:8080/openapi/greeting/dave' \\\n -H 'accept: text/plain' \\\n -H 'Content-Type: application/json' \\\n -d '{}'\n\"\"\"\n\nfrom pathlib import Path\n\nimport connexion\n\n\ndef oas_http_svc():\n \"\"\"Start the OAS 3.0 OpenAPI HTTP service\"\"\"\n print(\"http://localhost:8080/oas3/ui/\")\n specification_dir = Path(__file__).parent.parent.parent / \"docs/.well-known\"\n app = connexion.AsyncApp(__name__, specification_dir=str(specification_dir))\n app.add_api(\"metagpt_oas3_api.yaml\")\n app.add_api(\"openapi.yaml\")\n app.run(port=8080)\n\n\nif __name__ == \"__main__\":\n oas_http_svc()\n\n```\n\n---\nTranslate the code above into Mermaid Sequence Diagram.": "To translate the given Python code into a Mermaid Sequence Diagram, we need to understand the flow of the code and identify the interactions between different components. Here's the translated code into a Mermaid Sequence Diagram:\n\n```mermaid\nsequenceDiagram\n participant User\n participant metagpt_oas3_api_svc.py\n participant connexion\n participant metagpt_oas3_api.yaml\n participant openapi.yaml\n\n User->>metagpt_oas3_api_svc.py: Start the OAS 3.0 OpenAPI HTTP service\n metagpt_oas3_api_svc.py->>connexion: Create an AsyncApp instance\n metagpt_oas3_api_svc.py->>connexion: Add the metagpt_oas3_api.yaml specification\n metagpt_oas3_api_svc.py->>connexion: Add the openapi.yaml specification\n metagpt_oas3_api_svc.py->>connexion: Run the HTTP service on port 8080\n connexion->>User: Display the URL for accessing the OAS 3.0 UI\n\n Note over metagpt_oas3_api_svc.py, connexion: The HTTP service is running on http://localhost:8080/oas3/ui/\n```\n\nIn the diagram, the User starts the OAS 3.0 OpenAPI HTTP service by executing the `oas_http_svc()` function in the `metagpt_oas3_api_svc.py` file. This function creates an instance of the `connexion.AsyncApp` class from the `connexion` library. The `metagpt_oas3_api.yaml` and `openapi.yaml` specifications are added to the app. Finally, the HTTP service is run on port 8080, and the URL for accessing the OAS 3.0 UI is displayed to the User.",
@@ -44,8 +44,8 @@
"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._init_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.rc.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.": "Here is the Mermaid Sequence Diagram representation of the provided code:\n\n```mermaid\nsequenceDiagram\n participant Researcher\n participant Action\n participant CollectLinks\n participant WebBrowseAndSummarize\n participant ConductResearch\n participant Message\n participant Report\n\n Researcher->>Action: Initialize actions\n Researcher->>Action: Set react mode\n Researcher->>Researcher: Check language compatibility\n Researcher->>Researcher: Think\n Researcher->>Action: Act\n Action->>Message: Get message from memory\n Message-->>Action: Return message\n Action->>Researcher: Act on message\n Researcher->>CollectLinks: Run CollectLinks action\n CollectLinks->>CollectLinks: Collect links\n CollectLinks-->>Researcher: Return links\n Researcher->>Message: Create Report message\n Message-->>Researcher: Return Report message\n Researcher->>WebBrowseAndSummarize: Run WebBrowseAndSummarize action\n WebBrowseAndSummarize->>WebBrowseAndSummarize: Browse and summarize links\n WebBrowseAndSummarize-->>Researcher: Return summaries\n Researcher->>Message: Create Report message\n Message-->>Researcher: Return Report message\n Researcher->>ConductResearch: Run ConductResearch action\n ConductResearch->>ConductResearch: Conduct research\n ConductResearch-->>Researcher: Return research content\n Researcher->>Message: Create Report message\n Message-->>Researcher: Return Report message\n Researcher->>Researcher: Add message to memory\n Researcher->>Researcher: Think\n Researcher->>Action: Act\n Action->>Message: Get message from memory\n Message-->>Action: Return message\n Action->>Researcher: Act on message\n Researcher->>Researcher: Write report\n Researcher->>Message: Return message\n```\n\nPlease note that this is a simplified representation of the code logic and may not include all the details.",
"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\nfrom metagpt.logs import logger\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 , 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\ndef main():\n prompt = WRITE_PRD_NODE.compile(context=\"\")\n logger.info(prompt)\n\n\nif __name__ == \"__main__\":\n main()\n\n```\n\n---\nTranslate the code above into Mermaid Sequence Diagram.": "```mermaid\nsequenceDiagram\n participant Language\n participant Programming Language\n participant Original Requirements\n participant Project Name\n participant Product Goals\n participant User Stories\n participant Competitive Analysis\n participant Competitive Quadrant Chart\n participant Requirement Analysis\n participant Requirement Pool\n participant UI Design draft\n participant Anything UNCLEAR\n participant issue_type\n participant is_relative\n participant reason\n participant WritePRD\n participant WP_ISSUE_TYPE\n participant WP_IS_RELATIVE\n \n WritePRD->>Language: Provide the language used in the project, typically matching the user's requirement language.\n WritePRD->>Programming Language: Python/JavaScript or other mainstream programming language.\n WritePRD->>Original Requirements: Place the original user's requirements here.\n WritePRD->>Project Name: According to the content of \"Original Requirements,\" name the project using snake case style , like 'game_2048' or 'simple_crm.\n WritePRD->>Product Goals: Provide up to three clear, orthogonal product goals.\n WritePRD->>User Stories: Provide up to 3 to 5 scenario-based user stories.\n WritePRD->>Competitive Analysis: Provide 5 to 7 competitive products.\n WritePRD->>Competitive Quadrant Chart: Use mermaid quadrantChart syntax. Distribute scores evenly between 0 and 1\n WritePRD->>Requirement Analysis: Provide a detailed analysis of the requirements.\n WritePRD->>Requirement Pool: List down the top-5 requirements with their priority (P0, P1, P2).\n WritePRD->>UI Design draft: Provide a simple description of UI elements, functions, style, and layout.\n WritePRD->>Anything UNCLEAR: Mention any aspects of the project that are unclear and try to clarify them.\n \n WritePRD->>WP_ISSUE_TYPE: Answer BUG/REQUIREMENT. If it is a bugfix, answer BUG, otherwise answer Requirement\n WP_ISSUE_TYPE->>issue_type: BUG\n WP_ISSUE_TYPE->>reason: Explain the reasoning process from question to answer\n \n WritePRD->>WP_IS_RELATIVE: Answer YES/NO. If the requirement is related to the old PRD, answer YES, otherwise NO\n WP_IS_RELATIVE->>is_relative: YES\n WP_IS_RELATIVE->>reason: Explain the reasoning process from question to answer\n \n WritePRD-->>Language: expected_type: str\n WritePRD-->>Programming Language: expected_type: str\n WritePRD-->>Original Requirements: expected_type: str\n WritePRD-->>Project Name: expected_type: str\n WritePRD-->>Product Goals: expected_type: List[str]\n WritePRD-->>User Stories: expected_type: List[str]\n WritePRD-->>Competitive Analysis: expected_type: List[str]\n WritePRD-->>Competitive Quadrant Chart: expected_type: str\n WritePRD-->>Requirement Analysis: expected_type: str\n WritePRD-->>Requirement Pool: expected_type: List[List[str]]\n WritePRD-->>UI Design draft: expected_type: str\n WritePRD-->>Anything UNCLEAR: expected_type: str\n \n WP_ISSUE_TYPE-->>issue_type: expected_type: str\n WP_ISSUE_TYPE-->>reason: expected_type: str\n \n WP_IS_RELATIVE-->>is_relative: expected_type: str\n WP_IS_RELATIVE-->>reason: expected_type: str\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 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 Script\n participant Action\n participant OutputParser\n participant ast\n participant fire\n\n User->>Script: Run script with arguments\n Script->>Action: Call run() method\n Action->>OutputParser: Parse system text\n Action->>ast: Parse code into AST\n Action->>Action: Simplify code\n Action->>Action: Generate system text\n Action->>OutputParser: Parse documented code\n Action->>Action: Merge docstrings\n Action->>Script: Return code with docstrings\n Script->>fire: Call write_docstring() method\n fire->>Action: Call write_docstring() method\n Action->>OutputParser: Parse code from file\n Action->>Action: Run run() method\n Action->>Action: Write docstrings\n Action->>OutputParser: Parse code with docstrings\n Action->>Script: Return code with docstrings\n Script->>User: Return code with docstrings\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@Author : alexanderwu\n@File : write_review.py\n\"\"\"\nimport asyncio\nfrom typing import List\n\nfrom metagpt.actions import Action\nfrom metagpt.actions.action_node import ActionNode\n\nREVIEW = ActionNode(\n key=\"Review\",\n expected_type=List[str],\n instruction=\"Act as an experienced reviewer and critically assess the given output. Provide specific and\"\n \" constructive feedback, highlighting areas for improvement and suggesting changes.\",\n example=[\n \"The logic in the function `calculate_total` seems flawed. Shouldn't it consider the discount rate as well?\",\n \"The TODO function is not implemented yet? Should we implement it before commit?\",\n ],\n)\n\nLGTM = ActionNode(\n key=\"LGTM\",\n expected_type=str,\n instruction=\"LGTM/LBTM. If the code is fully implemented, \"\n \"give a LGTM (Looks Good To Me), otherwise provide a LBTM (Looks Bad To Me).\",\n example=\"LBTM\",\n)\n\nACTIONS = ActionNode(\n key=\"Actions\",\n expected_type=str,\n instruction=\"Based on the code review outcome, suggest actionable steps. This can include code changes, \"\n \"refactoring suggestions, or any follow-up tasks.\",\n example=\"\"\"1. Refactor the `process_data` method to improve readability and efficiency.\n2. Cover edge cases in the `validate_user` function.\n3. Implement a the TODO in the `calculate_total` function.\n4. 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 ```\n\"\"\",\n)\n\nWRITE_DRAFT = ActionNode(\n key=\"WriteDraft\",\n expected_type=str,\n instruction=\"Could you write draft code for move function in order to implement it?\",\n example=\"Draft: ...\",\n)\n\n\nWRITE_MOVE_FUNCTION = ActionNode(\n key=\"WriteFunction\",\n expected_type=str,\n instruction=\"write code for the function not implemented.\",\n example=\"\"\"\n```Code\n...\n```\n\"\"\",\n)\n\n\nREWRITE_CODE = ActionNode(\n key=\"RewriteCode\",\n expected_type=str,\n instruction=\"\"\"rewrite code based on the Review and Actions\"\"\",\n example=\"\"\"\n```python\n## example.py\ndef calculate_total(price, quantity):\n total = price * quantity\n```\n\"\"\",\n)\n\n\nCODE_REVIEW_CONTEXT = \"\"\"\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.\n\n# Context\n## System Design\n{\"Implementation approach\": \"我们将使用HTML、CSS和JavaScript来实现这个单机的响应式2048游戏。为了确保游戏性能流畅和响应式设计,我们会选择使用Vue.js框架,因为它易于上手且适合构建交互式界面。我们还将使用localStorage来记录玩家的最高分。\", \"File list\": [\"index.html\", \"styles.css\", \"main.js\", \"game.js\", \"storage.js\"], \"Data structures and interfaces\": \"classDiagram\\\n class Game {\\\n -board Array\\\n -score Number\\\n -bestScore Number\\\n +constructor()\\\n +startGame()\\\n +move(direction: String)\\\n +getBoard() Array\\\n +getScore() Number\\\n +getBestScore() Number\\\n +setBestScore(score: Number)\\\n }\\\n class Storage {\\\n +getBestScore() Number\\\n +setBestScore(score: Number)\\\n }\\\n class Main {\\\n +init()\\\n +bindEvents()\\\n }\\\n Game --> Storage : uses\\\n Main --> Game : uses\", \"Program call flow\": \"sequenceDiagram\\\n participant M as Main\\\n participant G as Game\\\n participant S as Storage\\\n M->>G: init()\\\n G->>S: getBestScore()\\\n S-->>G: return bestScore\\\n M->>G: bindEvents()\\\n M->>G: startGame()\\\n loop Game Loop\\\n M->>G: move(direction)\\\n G->>S: setBestScore(score)\\\n S-->>G: return\\\n end\", \"Anything UNCLEAR\": \"目前项目要求明确,没有不清楚的地方。\"}\n\n## Tasks\n{\"Required Python packages\": [\"无需Python包\"], \"Required Other language third-party packages\": [\"vue.js\"], \"Logic Analysis\": [[\"index.html\", \"作为游戏的入口文件和主要的HTML结构\"], [\"styles.css\", \"包含所有的CSS样式,确保游戏界面美观\"], [\"main.js\", \"包含Main类,负责初始化游戏和绑定事件\"], [\"game.js\", \"包含Game类,负责游戏逻辑,如开始游戏、移动方块等\"], [\"storage.js\", \"包含Storage类,用于获取和设置玩家的最高分\"]], \"Task list\": [\"index.html\", \"styles.css\", \"storage.js\", \"game.js\", \"main.js\"], \"Full API spec\": \"\", \"Shared Knowledge\": \"\\'game.js\\' 包含游戏逻辑相关的函数,被 \\'main.js\\' 调用。\", \"Anything UNCLEAR\": \"目前项目要求明确,没有不清楚的地方。\"}\n\n## Code Files\n----- index.html\n\n\n\n \n \n 2048游戏\n \n \n\n\n
\n
2048
\n
\n
\n
分数
\n
{{ score }}
\n
\n
\n
最高分
\n
{{ bestScore }}
\n
\n
\n
\n
\n
\n {{ cell !== 0 ? cell : \\'\\' }}\n
\n
\n
\n \n
\n\n \n \n \n \n\n\n\n----- styles.css\n/* styles.css */\nbody, html {\n margin: 0;\n padding: 0;\n font-family: \\'Arial\\', sans-serif;\n}\n\n#app {\n text-align: center;\n font-size: 18px;\n color: #776e65;\n}\n\nh1 {\n color: #776e65;\n font-size: 72px;\n font-weight: bold;\n margin: 20px 0;\n}\n\n.scores-container {\n display: flex;\n justify-content: center;\n margin-bottom: 20px;\n}\n\n.score-container, .best-container {\n background: #bbada0;\n padding: 10px;\n border-radius: 5px;\n margin: 0 10px;\n min-width: 100px;\n text-align: center;\n}\n\n.score-header, .best-header {\n color: #eee4da;\n font-size: 18px;\n margin-bottom: 5px;\n}\n\n.game-container {\n max-width: 500px;\n margin: 0 auto 20px;\n background: #bbada0;\n padding: 15px;\n border-radius: 10px;\n position: relative;\n}\n\n.grid-row {\n display: flex;\n}\n\n.grid-cell {\n background: #cdc1b4;\n width: 100px;\n height: 100px;\n margin: 5px;\n display: flex;\n justify-content: center;\n align-items: center;\n font-size: 35px;\n font-weight: bold;\n color: #776e65;\n border-radius: 3px;\n}\n\n/* Dynamic classes for different number cells */\n.number-cell-2 {\n background: #eee4da;\n}\n\n.number-cell-4 {\n background: #ede0c8;\n}\n\n.number-cell-8 {\n background: #f2b179;\n color: #f9f6f2;\n}\n\n.number-cell-16 {\n background: #f59563;\n color: #f9f6f2;\n}\n\n.number-cell-32 {\n background: #f67c5f;\n color: #f9f6f2;\n}\n\n.number-cell-64 {\n background: #f65e3b;\n color: #f9f6f2;\n}\n\n.number-cell-128 {\n background: #edcf72;\n color: #f9f6f2;\n}\n\n.number-cell-256 {\n background: #edcc61;\n color: #f9f6f2;\n}\n\n.number-cell-512 {\n background: #edc850;\n color: #f9f6f2;\n}\n\n.number-cell-1024 {\n background: #edc53f;\n color: #f9f6f2;\n}\n\n.number-cell-2048 {\n background: #edc22e;\n color: #f9f6f2;\n}\n\n/* Larger numbers need smaller font sizes */\n.number-cell-1024, .number-cell-2048 {\n font-size: 30px;\n}\n\nbutton {\n background-color: #8f7a66;\n color: #f9f6f2;\n border: none;\n border-radius: 3px;\n padding: 10px 20px;\n font-size: 18px;\n cursor: pointer;\n outline: none;\n}\n\nbutton:hover {\n background-color: #9f8b76;\n}\n\n----- storage.js\n## storage.js\nclass Storage {\n // 获取最高分\n getBestScore() {\n // 尝试从localStorage中获取最高分,如果不存在则默认为0\n const bestScore = localStorage.getItem(\\'bestScore\\');\n return bestScore ? Number(bestScore) : 0;\n }\n\n // 设置最高分\n setBestScore(score) {\n // 将最高分设置到localStorage中\n localStorage.setItem(\\'bestScore\\', score.toString());\n }\n}\n\n\n\n## Code to be Reviewed: game.js\n```Code\n## game.js\nclass Game {\n constructor() {\n this.board = this.createEmptyBoard();\n this.score = 0;\n this.bestScore = 0;\n }\n\n createEmptyBoard() {\n const board = [];\n for (let i = 0; i < 4; i++) {\n board[i] = [0, 0, 0, 0];\n }\n return board;\n }\n\n startGame() {\n this.board = this.createEmptyBoard();\n this.score = 0;\n this.addRandomTile();\n this.addRandomTile();\n }\n\n addRandomTile() {\n let emptyCells = [];\n for (let r = 0; r < 4; r++) {\n for (let c = 0; c < 4; c++) {\n if (this.board[r][c] === 0) {\n emptyCells.push({ r, c });\n }\n }\n }\n if (emptyCells.length > 0) {\n let randomCell = emptyCells[Math.floor(Math.random() * emptyCells.length)];\n this.board[randomCell.r][randomCell.c] = Math.random() < 0.9 ? 2 : 4;\n }\n }\n\n move(direction) {\n // This function will handle the logic for moving tiles\n // in the specified direction and merging them\n // It will also update the score and add a new random tile if the move is successful\n // The actual implementation of this function is complex and would require\n // a significant amount of code to handle all the cases for moving and merging tiles\n // For the purposes of this example, we will not implement the full logic\n // Instead, we will just call addRandomTile to simulate a move\n this.addRandomTile();\n }\n\n getBoard() {\n return this.board;\n }\n\n getScore() {\n return this.score;\n }\n\n getBestScore() {\n return this.bestScore;\n }\n\n setBestScore(score) {\n this.bestScore = score;\n }\n}\n\n```\n\"\"\"\n\n\nCODE_REVIEW_SMALLEST_CONTEXT = \"\"\"\n## Code to be Reviewed: game.js\n```Code\n// game.js\nclass Game {\n constructor() {\n this.board = this.createEmptyBoard();\n this.score = 0;\n this.bestScore = 0;\n }\n\n createEmptyBoard() {\n const board = [];\n for (let i = 0; i < 4; i++) {\n board[i] = [0, 0, 0, 0];\n }\n return board;\n }\n\n startGame() {\n this.board = this.createEmptyBoard();\n this.score = 0;\n this.addRandomTile();\n this.addRandomTile();\n }\n\n addRandomTile() {\n let emptyCells = [];\n for (let r = 0; r < 4; r++) {\n for (let c = 0; c < 4; c++) {\n if (this.board[r][c] === 0) {\n emptyCells.push({ r, c });\n }\n }\n }\n if (emptyCells.length > 0) {\n let randomCell = emptyCells[Math.floor(Math.random() * emptyCells.length)];\n this.board[randomCell.r][randomCell.c] = Math.random() < 0.9 ? 2 : 4;\n }\n }\n\n move(direction) {\n // This function will handle the logic for moving tiles\n // in the specified direction and merging them\n // It will also update the score and add a new random tile if the move is successful\n // The actual implementation of this function is complex and would require\n // a significant amount of code to handle all the cases for moving and merging tiles\n // For the purposes of this example, we will not implement the full logic\n // Instead, we will just call addRandomTile to simulate a move\n this.addRandomTile();\n }\n\n getBoard() {\n return this.board;\n }\n\n getScore() {\n return this.score;\n }\n\n getBestScore() {\n return this.bestScore;\n }\n\n setBestScore(score) {\n this.bestScore = score;\n }\n}\n\n```\n\"\"\"\n\n\nCODE_REVIEW_SAMPLE = \"\"\"\n## Code Review: game.js\n1. The code partially implements the requirements. The `Game` class is missing the full implementation of the `move` method, which is crucial for the game\\'s functionality.\n2. The code logic is not completely correct. The `move` method is not implemented, which means the game cannot process player moves.\n3. The existing code follows the \"Data structures and interfaces\" in terms of class structure but lacks full method implementations.\n4. Not all functions are implemented. The `move` method is incomplete and does not handle the logic for moving and merging tiles.\n5. All necessary pre-dependencies seem to be imported since the code does not indicate the need for additional imports.\n6. The methods from other files (such as `Storage`) are not being used in the provided code snippet, but the class structure suggests that they will be used correctly.\n\n## Actions\n1. Implement the `move` method to handle tile movements and merging. This is a complex task that requires careful consideration of the game\\'s rules and logic. Here is a simplified version of how one might begin to implement the `move` method:\n ```javascript\n move(direction) {\n // Simplified logic for moving tiles up\n if (direction === \\'up\\') {\n for (let col = 0; col < 4; col++) {\n let tiles = this.board.map(row => row[col]).filter(val => val !== 0);\n let merged = [];\n for (let i = 0; i < tiles.length; i++) {\n if (tiles[i] === tiles[i + 1]) {\n tiles[i] *= 2;\n this.score += tiles[i];\n tiles[i + 1] = 0;\n merged.push(i);\n }\n }\n tiles = tiles.filter(val => val !== 0);\n while (tiles.length < 4) {\n tiles.push(0);\n }\n for (let row = 0; row < 4; row++) {\n this.board[row][col] = tiles[row];\n }\n }\n }\n // Additional logic needed for \\'down\\', \\'left\\', \\'right\\'\n // ...\n this.addRandomTile();\n }\n ```\n2. Integrate the `Storage` class methods to handle the best score. This means updating the `startGame` and `setBestScore` methods to use `Storage` for retrieving and setting the best score:\n ```javascript\n startGame() {\n this.board = this.createEmptyBoard();\n this.score = 0;\n this.bestScore = new Storage().getBestScore(); // Retrieve the best score from storage\n this.addRandomTile();\n this.addRandomTile();\n }\n\n setBestScore(score) {\n if (score > this.bestScore) {\n this.bestScore = score;\n new Storage().setBestScore(score); // Set the new best score in storage\n }\n }\n ```\n\n## Code Review Result\nLBTM\n\n```\n\"\"\"\n\n\nWRITE_CODE_NODE = ActionNode.from_children(\"WRITE_REVIEW_NODE\", [REVIEW, LGTM, ACTIONS])\nWRITE_MOVE_NODE = ActionNode.from_children(\"WRITE_MOVE_NODE\", [WRITE_DRAFT, WRITE_MOVE_FUNCTION])\n\n\nCR_FOR_MOVE_FUNCTION_BY_3 = \"\"\"\nThe move function implementation provided appears to be well-structured and follows a clear logic for moving and merging tiles in the specified direction. However, there are a few potential improvements that could be made to enhance the code:\n\n1. Encapsulation: The logic for moving and merging tiles could be encapsulated into smaller, reusable functions to improve readability and maintainability.\n\n2. Magic Numbers: There are some magic numbers (e.g., 4, 3) used in the loops that could be replaced with named constants for improved readability and easier maintenance.\n\n3. Comments: Adding comments to explain the logic and purpose of each section of the code can improve understanding for future developers who may need to work on or maintain the code.\n\n4. Error Handling: It's important to consider error handling for unexpected input or edge cases to ensure the function behaves as expected in all scenarios.\n\nOverall, the code could benefit from refactoring to improve readability, maintainability, and extensibility. If you would like, I can provide a refactored version of the move function that addresses these considerations.\n\"\"\"\n\n\nclass WriteCodeAN(Action):\n \"\"\"Write a code review for the context.\"\"\"\n\n async def run(self, context):\n self.llm.system_prompt = \"You are an outstanding engineer and can implement any code\"\n return await WRITE_MOVE_FUNCTION.fill(context=context, llm=self.llm, schema=\"json\")\n # return await WRITE_CODE_NODE.fill(context=context, llm=self.llm, schema=\"markdown\")\n\n\nasync def main():\n await WriteCodeAN().run(CODE_REVIEW_SMALLEST_CONTEXT)\n\n\nif __name__ == \"__main__\":\n asyncio.run(main())\n\n```\n\n---\nTranslate the code above into Mermaid Sequence Diagram.": "```mermaid\nsequenceDiagram\n participant User\n participant CodeReview\n participant WriteCodeAN\n participant WRITE_MOVE_FUNCTION\n\n User->>CodeReview: Request code review\n CodeReview->>WriteCodeAN: Run WriteCodeAN action\n WriteCodeAN->>WRITE_MOVE_FUNCTION: Fill WRITE_MOVE_FUNCTION node\n WRITE_MOVE_FUNCTION-->>WriteCodeAN: Return filled node\n WriteCodeAN-->>CodeReview: Return filled node\n CodeReview-->>User: Return code review result\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/12/14 15:28\n@Author : alexanderwu\n@File : project_management_an.py\n\"\"\"\nfrom typing import List\n\nfrom metagpt.actions.action_node import ActionNode\nfrom metagpt.logs import logger\n\nREQUIRED_PYTHON_PACKAGES = ActionNode(\n key=\"Required Python packages\",\n expected_type=List[str],\n instruction=\"Provide required Python packages in requirements.txt format.\",\n example=[\"flask==1.1.2\", \"bcrypt==3.2.0\"],\n)\n\nREQUIRED_OTHER_LANGUAGE_PACKAGES = ActionNode(\n key=\"Required Other language third-party packages\",\n expected_type=List[str],\n instruction=\"List down the required packages for languages other than Python.\",\n example=[\"No third-party dependencies required\"],\n)\n\nLOGIC_ANALYSIS = ActionNode(\n key=\"Logic Analysis\",\n expected_type=List[List[str]],\n instruction=\"Provide a list of files with the classes/methods/functions to be implemented, \"\n \"including dependency analysis and imports.\",\n example=[\n [\"game.py\", \"Contains Game class and ... functions\"],\n [\"main.py\", \"Contains main function, from game import Game\"],\n ],\n)\n\nTASK_LIST = ActionNode(\n key=\"Task list\",\n expected_type=List[str],\n instruction=\"Break down the tasks into a list of filenames, prioritized by dependency order.\",\n example=[\"game.py\", \"main.py\"],\n)\n\nFULL_API_SPEC = ActionNode(\n key=\"Full API spec\",\n expected_type=str,\n instruction=\"Describe all APIs using OpenAPI 3.0 spec that may be used by both frontend and backend. If front-end \"\n \"and back-end communication is not required, leave it blank.\",\n example=\"openapi: 3.0.0 ...\",\n)\n\nSHARED_KNOWLEDGE = ActionNode(\n key=\"Shared Knowledge\",\n expected_type=str,\n instruction=\"Detail any shared knowledge, like common utility functions or configuration variables.\",\n example=\"'game.py' contains functions shared across the project.\",\n)\n\nANYTHING_UNCLEAR_PM = ActionNode(\n key=\"Anything UNCLEAR\",\n expected_type=str,\n instruction=\"Mention any unclear aspects in the project management context and try to clarify them.\",\n example=\"Clarification needed on how to start and initialize third-party libraries.\",\n)\n\nNODES = [\n REQUIRED_PYTHON_PACKAGES,\n REQUIRED_OTHER_LANGUAGE_PACKAGES,\n LOGIC_ANALYSIS,\n TASK_LIST,\n FULL_API_SPEC,\n SHARED_KNOWLEDGE,\n ANYTHING_UNCLEAR_PM,\n]\n\n\nPM_NODE = ActionNode.from_children(\"PM_NODE\", NODES)\n\n\ndef main():\n prompt = PM_NODE.compile(context=\"\")\n logger.info(prompt)\n\n\nif __name__ == \"__main__\":\n main()\n\n```\n\n---\nTranslate the code above into Mermaid Sequence Diagram.": "```mermaid\nsequenceDiagram\n participant ActionNode\n participant PM_NODE\n participant main\n\n main->>PM_NODE: compile(context=\"\")\n PM_NODE->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>Action",
+ "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@Author : alexanderwu\n@File : write_review.py\n\"\"\"\nimport asyncio\nfrom typing import List\n\nfrom metagpt.actions import Action\nfrom metagpt.actions.action_node import ActionNode\n\nREVIEW = ActionNode(\n key=\"Review\",\n expected_type=List[str],\n instruction=\"Act as an experienced reviewer and critically assess the given output. Provide specific and\"\n \" constructive feedback, highlighting areas for improvement and suggesting changes.\",\n example=[\n \"The logic in the function `calculate_total` seems flawed. Shouldn't it consider the discount rate as well?\",\n \"The TODO function is not implemented yet? Should we implement it before commit?\",\n ],\n)\n\nLGTM = ActionNode(\n key=\"LGTM\",\n expected_type=str,\n instruction=\"LGTM/LBTM. If the code is fully implemented, \"\n \"give a LGTM (Looks Good To Me), otherwise provide a LBTM (Looks Bad To Me).\",\n example=\"LBTM\",\n)\n\nACTIONS = ActionNode(\n key=\"Actions\",\n expected_type=str,\n instruction=\"Based on the code review outcome, suggest actionable steps. This can include code changes, \"\n \"refactoring suggestions, or any follow-up tasks.\",\n example=\"\"\"1. Refactor the `process_data` method to improve readability and efficiency.\n2. Cover edge cases in the `validate_user` function.\n3. Implement a the TODO in the `calculate_total` function.\n4. 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 ```\n\"\"\",\n)\n\nWRITE_DRAFT = ActionNode(\n key=\"WriteDraft\",\n expected_type=str,\n instruction=\"Could you write draft code for move function in order to implement it?\",\n example=\"Draft: ...\",\n)\n\n\nWRITE_MOVE_FUNCTION = ActionNode(\n key=\"WriteFunction\",\n expected_type=str,\n instruction=\"write code for the function not implemented.\",\n example=\"\"\"\n```Code\n...\n```\n\"\"\",\n)\n\n\nREWRITE_CODE = ActionNode(\n key=\"RewriteCode\",\n expected_type=str,\n instruction=\"\"\"rewrite code based on the Review and Actions\"\"\",\n example=\"\"\"\n```python\n## example.py\ndef calculate_total(price, quantity):\n total = price * quantity\n```\n\"\"\",\n)\n\n\nCODE_REVIEW_CONTEXT = \"\"\"\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.\n\n# Context\n## System Design\n{\"Implementation approach\": \"我们将使用HTML、CSS和JavaScript来实现这个单机的响应式2048游戏。为了确保游戏性能流畅和响应式设计,我们会选择使用Vue.js框架,因为它易于上手且适合构建交互式界面。我们还将使用localStorage来记录玩家的最高分。\", \"File list\": [\"index.html\", \"styles.css\", \"main.js\", \"game.js\", \"storage.js\"], \"Data structures and interfaces\": \"classDiagram\\\n class Game {\\\n -board Array\\\n -score Number\\\n -bestScore Number\\\n +constructor()\\\n +startGame()\\\n +move(direction: String)\\\n +getBoard() Array\\\n +getScore() Number\\\n +getBestScore() Number\\\n +setBestScore(score: Number)\\\n }\\\n class Storage {\\\n +getBestScore() Number\\\n +setBestScore(score: Number)\\\n }\\\n class Main {\\\n +init()\\\n +bindEvents()\\\n }\\\n Game --> Storage : uses\\\n Main --> Game : uses\", \"Program call flow\": \"sequenceDiagram\\\n participant M as Main\\\n participant G as Game\\\n participant S as Storage\\\n M->>G: init()\\\n G->>S: getBestScore()\\\n S-->>G: return bestScore\\\n M->>G: bindEvents()\\\n M->>G: startGame()\\\n loop Game Loop\\\n M->>G: move(direction)\\\n G->>S: setBestScore(score)\\\n S-->>G: return\\\n end\", \"Anything UNCLEAR\": \"目前项目要求明确,没有不清楚的地方。\"}\n\n## Tasks\n{\"Required packages\": [\"无需Python包\"], \"Required Other language third-party packages\": [\"vue.js\"], \"Logic Analysis\": [[\"index.html\", \"作为游戏的入口文件和主要的HTML结构\"], [\"styles.css\", \"包含所有的CSS样式,确保游戏界面美观\"], [\"main.js\", \"包含Main类,负责初始化游戏和绑定事件\"], [\"game.js\", \"包含Game类,负责游戏逻辑,如开始游戏、移动方块等\"], [\"storage.js\", \"包含Storage类,用于获取和设置玩家的最高分\"]], \"Task list\": [\"index.html\", \"styles.css\", \"storage.js\", \"game.js\", \"main.js\"], \"Full API spec\": \"\", \"Shared Knowledge\": \"\\'game.js\\' 包含游戏逻辑相关的函数,被 \\'main.js\\' 调用。\", \"Anything UNCLEAR\": \"目前项目要求明确,没有不清楚的地方。\"}\n\n## Code Files\n----- index.html\n\n\n\n \n \n 2048游戏\n \n \n\n\n
\n
2048
\n
\n
\n
分数
\n
{{ score }}
\n
\n
\n
最高分
\n
{{ bestScore }}
\n
\n
\n
\n
\n
\n {{ cell !== 0 ? cell : \\'\\' }}\n
\n
\n
\n \n
\n\n \n \n \n \n\n\n\n----- styles.css\n/* styles.css */\nbody, html {\n margin: 0;\n padding: 0;\n font-family: \\'Arial\\', sans-serif;\n}\n\n#app {\n text-align: center;\n font-size: 18px;\n color: #776e65;\n}\n\nh1 {\n color: #776e65;\n font-size: 72px;\n font-weight: bold;\n margin: 20px 0;\n}\n\n.scores-container {\n display: flex;\n justify-content: center;\n margin-bottom: 20px;\n}\n\n.score-container, .best-container {\n background: #bbada0;\n padding: 10px;\n border-radius: 5px;\n margin: 0 10px;\n min-width: 100px;\n text-align: center;\n}\n\n.score-header, .best-header {\n color: #eee4da;\n font-size: 18px;\n margin-bottom: 5px;\n}\n\n.game-container {\n max-width: 500px;\n margin: 0 auto 20px;\n background: #bbada0;\n padding: 15px;\n border-radius: 10px;\n position: relative;\n}\n\n.grid-row {\n display: flex;\n}\n\n.grid-cell {\n background: #cdc1b4;\n width: 100px;\n height: 100px;\n margin: 5px;\n display: flex;\n justify-content: center;\n align-items: center;\n font-size: 35px;\n font-weight: bold;\n color: #776e65;\n border-radius: 3px;\n}\n\n/* Dynamic classes for different number cells */\n.number-cell-2 {\n background: #eee4da;\n}\n\n.number-cell-4 {\n background: #ede0c8;\n}\n\n.number-cell-8 {\n background: #f2b179;\n color: #f9f6f2;\n}\n\n.number-cell-16 {\n background: #f59563;\n color: #f9f6f2;\n}\n\n.number-cell-32 {\n background: #f67c5f;\n color: #f9f6f2;\n}\n\n.number-cell-64 {\n background: #f65e3b;\n color: #f9f6f2;\n}\n\n.number-cell-128 {\n background: #edcf72;\n color: #f9f6f2;\n}\n\n.number-cell-256 {\n background: #edcc61;\n color: #f9f6f2;\n}\n\n.number-cell-512 {\n background: #edc850;\n color: #f9f6f2;\n}\n\n.number-cell-1024 {\n background: #edc53f;\n color: #f9f6f2;\n}\n\n.number-cell-2048 {\n background: #edc22e;\n color: #f9f6f2;\n}\n\n/* Larger numbers need smaller font sizes */\n.number-cell-1024, .number-cell-2048 {\n font-size: 30px;\n}\n\nbutton {\n background-color: #8f7a66;\n color: #f9f6f2;\n border: none;\n border-radius: 3px;\n padding: 10px 20px;\n font-size: 18px;\n cursor: pointer;\n outline: none;\n}\n\nbutton:hover {\n background-color: #9f8b76;\n}\n\n----- storage.js\n## storage.js\nclass Storage {\n // 获取最高分\n getBestScore() {\n // 尝试从localStorage中获取最高分,如果不存在则默认为0\n const bestScore = localStorage.getItem(\\'bestScore\\');\n return bestScore ? Number(bestScore) : 0;\n }\n\n // 设置最高分\n setBestScore(score) {\n // 将最高分设置到localStorage中\n localStorage.setItem(\\'bestScore\\', score.toString());\n }\n}\n\n\n\n## Code to be Reviewed: game.js\n```Code\n## game.js\nclass Game {\n constructor() {\n this.board = this.createEmptyBoard();\n this.score = 0;\n this.bestScore = 0;\n }\n\n createEmptyBoard() {\n const board = [];\n for (let i = 0; i < 4; i++) {\n board[i] = [0, 0, 0, 0];\n }\n return board;\n }\n\n startGame() {\n this.board = this.createEmptyBoard();\n this.score = 0;\n this.addRandomTile();\n this.addRandomTile();\n }\n\n addRandomTile() {\n let emptyCells = [];\n for (let r = 0; r < 4; r++) {\n for (let c = 0; c < 4; c++) {\n if (this.board[r][c] === 0) {\n emptyCells.push({ r, c });\n }\n }\n }\n if (emptyCells.length > 0) {\n let randomCell = emptyCells[Math.floor(Math.random() * emptyCells.length)];\n this.board[randomCell.r][randomCell.c] = Math.random() < 0.9 ? 2 : 4;\n }\n }\n\n move(direction) {\n // This function will handle the logic for moving tiles\n // in the specified direction and merging them\n // It will also update the score and add a new random tile if the move is successful\n // The actual implementation of this function is complex and would require\n // a significant amount of code to handle all the cases for moving and merging tiles\n // For the purposes of this example, we will not implement the full logic\n // Instead, we will just call addRandomTile to simulate a move\n this.addRandomTile();\n }\n\n getBoard() {\n return this.board;\n }\n\n getScore() {\n return this.score;\n }\n\n getBestScore() {\n return this.bestScore;\n }\n\n setBestScore(score) {\n this.bestScore = score;\n }\n}\n\n```\n\"\"\"\n\n\nCODE_REVIEW_SMALLEST_CONTEXT = \"\"\"\n## Code to be Reviewed: game.js\n```Code\n// game.js\nclass Game {\n constructor() {\n this.board = this.createEmptyBoard();\n this.score = 0;\n this.bestScore = 0;\n }\n\n createEmptyBoard() {\n const board = [];\n for (let i = 0; i < 4; i++) {\n board[i] = [0, 0, 0, 0];\n }\n return board;\n }\n\n startGame() {\n this.board = this.createEmptyBoard();\n this.score = 0;\n this.addRandomTile();\n this.addRandomTile();\n }\n\n addRandomTile() {\n let emptyCells = [];\n for (let r = 0; r < 4; r++) {\n for (let c = 0; c < 4; c++) {\n if (this.board[r][c] === 0) {\n emptyCells.push({ r, c });\n }\n }\n }\n if (emptyCells.length > 0) {\n let randomCell = emptyCells[Math.floor(Math.random() * emptyCells.length)];\n this.board[randomCell.r][randomCell.c] = Math.random() < 0.9 ? 2 : 4;\n }\n }\n\n move(direction) {\n // This function will handle the logic for moving tiles\n // in the specified direction and merging them\n // It will also update the score and add a new random tile if the move is successful\n // The actual implementation of this function is complex and would require\n // a significant amount of code to handle all the cases for moving and merging tiles\n // For the purposes of this example, we will not implement the full logic\n // Instead, we will just call addRandomTile to simulate a move\n this.addRandomTile();\n }\n\n getBoard() {\n return this.board;\n }\n\n getScore() {\n return this.score;\n }\n\n getBestScore() {\n return this.bestScore;\n }\n\n setBestScore(score) {\n this.bestScore = score;\n }\n}\n\n```\n\"\"\"\n\n\nCODE_REVIEW_SAMPLE = \"\"\"\n## Code Review: game.js\n1. The code partially implements the requirements. The `Game` class is missing the full implementation of the `move` method, which is crucial for the game\\'s functionality.\n2. The code logic is not completely correct. The `move` method is not implemented, which means the game cannot process player moves.\n3. The existing code follows the \"Data structures and interfaces\" in terms of class structure but lacks full method implementations.\n4. Not all functions are implemented. The `move` method is incomplete and does not handle the logic for moving and merging tiles.\n5. All necessary pre-dependencies seem to be imported since the code does not indicate the need for additional imports.\n6. The methods from other files (such as `Storage`) are not being used in the provided code snippet, but the class structure suggests that they will be used correctly.\n\n## Actions\n1. Implement the `move` method to handle tile movements and merging. This is a complex task that requires careful consideration of the game\\'s rules and logic. Here is a simplified version of how one might begin to implement the `move` method:\n ```javascript\n move(direction) {\n // Simplified logic for moving tiles up\n if (direction === \\'up\\') {\n for (let col = 0; col < 4; col++) {\n let tiles = this.board.map(row => row[col]).filter(val => val !== 0);\n let merged = [];\n for (let i = 0; i < tiles.length; i++) {\n if (tiles[i] === tiles[i + 1]) {\n tiles[i] *= 2;\n this.score += tiles[i];\n tiles[i + 1] = 0;\n merged.push(i);\n }\n }\n tiles = tiles.filter(val => val !== 0);\n while (tiles.length < 4) {\n tiles.push(0);\n }\n for (let row = 0; row < 4; row++) {\n this.board[row][col] = tiles[row];\n }\n }\n }\n // Additional logic needed for \\'down\\', \\'left\\', \\'right\\'\n // ...\n this.addRandomTile();\n }\n ```\n2. Integrate the `Storage` class methods to handle the best score. This means updating the `startGame` and `setBestScore` methods to use `Storage` for retrieving and setting the best score:\n ```javascript\n startGame() {\n this.board = this.createEmptyBoard();\n this.score = 0;\n this.bestScore = new Storage().getBestScore(); // Retrieve the best score from storage\n this.addRandomTile();\n this.addRandomTile();\n }\n\n setBestScore(score) {\n if (score > this.bestScore) {\n this.bestScore = score;\n new Storage().setBestScore(score); // Set the new best score in storage\n }\n }\n ```\n\n## Code Review Result\nLBTM\n\n```\n\"\"\"\n\n\nWRITE_CODE_NODE = ActionNode.from_children(\"WRITE_REVIEW_NODE\", [REVIEW, LGTM, ACTIONS])\nWRITE_MOVE_NODE = ActionNode.from_children(\"WRITE_MOVE_NODE\", [WRITE_DRAFT, WRITE_MOVE_FUNCTION])\n\n\nCR_FOR_MOVE_FUNCTION_BY_3 = \"\"\"\nThe move function implementation provided appears to be well-structured and follows a clear logic for moving and merging tiles in the specified direction. However, there are a few potential improvements that could be made to enhance the code:\n\n1. Encapsulation: The logic for moving and merging tiles could be encapsulated into smaller, reusable functions to improve readability and maintainability.\n\n2. Magic Numbers: There are some magic numbers (e.g., 4, 3) used in the loops that could be replaced with named constants for improved readability and easier maintenance.\n\n3. Comments: Adding comments to explain the logic and purpose of each section of the code can improve understanding for future developers who may need to work on or maintain the code.\n\n4. Error Handling: It's important to consider error handling for unexpected input or edge cases to ensure the function behaves as expected in all scenarios.\n\nOverall, the code could benefit from refactoring to improve readability, maintainability, and extensibility. If you would like, I can provide a refactored version of the move function that addresses these considerations.\n\"\"\"\n\n\nclass WriteCodeAN(Action):\n \"\"\"Write a code review for the context.\"\"\"\n\n async def run(self, context):\n self.llm.system_prompt = \"You are an outstanding engineer and can implement any code\"\n return await WRITE_MOVE_FUNCTION.fill(context=context, llm=self.llm, schema=\"json\")\n # return await WRITE_CODE_NODE.fill(context=context, llm=self.llm, schema=\"markdown\")\n\n\nasync def main():\n await WriteCodeAN().run(CODE_REVIEW_SMALLEST_CONTEXT)\n\n\nif __name__ == \"__main__\":\n asyncio.run(main())\n\n```\n\n---\nTranslate the code above into Mermaid Sequence Diagram.": "```mermaid\nsequenceDiagram\n participant User\n participant CodeReview\n participant WriteCodeAN\n participant WRITE_MOVE_FUNCTION\n\n User->>CodeReview: Request code review\n CodeReview->>WriteCodeAN: Run WriteCodeAN action\n WriteCodeAN->>WRITE_MOVE_FUNCTION: Fill WRITE_MOVE_FUNCTION node\n WRITE_MOVE_FUNCTION-->>WriteCodeAN: Return filled node\n WriteCodeAN-->>CodeReview: Return filled node\n CodeReview-->>User: Return code review result\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/12/14 15:28\n@Author : alexanderwu\n@File : project_management_an.py\n\"\"\"\nfrom typing import List\n\nfrom metagpt.actions.action_node import ActionNode\nfrom metagpt.logs import logger\n\nREQUIRED_PYTHON_PACKAGES = ActionNode(\n key=\"Required packages\",\n expected_type=List[str],\n instruction=\"Provide Required packages in requirements.txt format.\",\n example=[\"flask==1.1.2\", \"bcrypt==3.2.0\"],\n)\n\nREQUIRED_OTHER_LANGUAGE_PACKAGES = ActionNode(\n key=\"Required Other language third-party packages\",\n expected_type=List[str],\n instruction=\"List down the required packages for languages other than Python.\",\n example=[\"No third-party dependencies required\"],\n)\n\nLOGIC_ANALYSIS = ActionNode(\n key=\"Logic Analysis\",\n expected_type=List[List[str]],\n instruction=\"Provide a list of files with the classes/methods/functions to be implemented, \"\n \"including dependency analysis and imports.\",\n example=[\n [\"game.py\", \"Contains Game class and ... functions\"],\n [\"main.py\", \"Contains main function, from game import Game\"],\n ],\n)\n\nTASK_LIST = ActionNode(\n key=\"Task list\",\n expected_type=List[str],\n instruction=\"Break down the tasks into a list of filenames, prioritized by dependency order.\",\n example=[\"game.py\", \"main.py\"],\n)\n\nFULL_API_SPEC = ActionNode(\n key=\"Full API spec\",\n expected_type=str,\n instruction=\"Describe all APIs using OpenAPI 3.0 spec that may be used by both frontend and backend. If front-end \"\n \"and back-end communication is not required, leave it blank.\",\n example=\"openapi: 3.0.0 ...\",\n)\n\nSHARED_KNOWLEDGE = ActionNode(\n key=\"Shared Knowledge\",\n expected_type=str,\n instruction=\"Detail any shared knowledge, like common utility functions or configuration variables.\",\n example=\"'game.py' contains functions shared across the project.\",\n)\n\nANYTHING_UNCLEAR_PM = ActionNode(\n key=\"Anything UNCLEAR\",\n expected_type=str,\n instruction=\"Mention any unclear aspects in the project management context and try to clarify them.\",\n example=\"Clarification needed on how to start and initialize third-party libraries.\",\n)\n\nNODES = [\n REQUIRED_PYTHON_PACKAGES,\n REQUIRED_OTHER_LANGUAGE_PACKAGES,\n LOGIC_ANALYSIS,\n TASK_LIST,\n FULL_API_SPEC,\n SHARED_KNOWLEDGE,\n ANYTHING_UNCLEAR_PM,\n]\n\n\nPM_NODE = ActionNode.from_children(\"PM_NODE\", NODES)\n\n\ndef main():\n prompt = PM_NODE.compile(context=\"\")\n logger.info(prompt)\n\n\nif __name__ == \"__main__\":\n main()\n\n```\n\n---\nTranslate the code above into Mermaid Sequence Diagram.": "```mermaid\nsequenceDiagram\n participant ActionNode\n participant PM_NODE\n participant main\n\n main->>PM_NODE: compile(context=\"\")\n PM_NODE->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>ActionNode: compile(context=\"\")\n ActionNode->>Action",
"You are a function parser. You can convert spoken words into function parameters.\n\n---\ntext_to_image function parameters description:\nparameter `text`: The text used for image conversion.\nparameter `size_type`: size type\n\n---\nExamples:\nIf want you to do `Draw a girl`, return `text_to_image(text=\"Draw a girl\", size_type=\"512x512\")` brief and clear.\nIf want you to do `Draw an apple`, return `text_to_image(text=\"Draw an apple\", size_type=\"512x512\")` brief and clear.\n\n---\n\nRefer to the `text_to_image` function description, and fill in the function parameters according to the example \"I want you to do xx\" in the Examples section.\nNow I want you to do `Draw an apple`, return function parameters in Examples format above, brief and clear.": "`text_to_image(text=\"Draw an apple\", size_type=\"512x512\")`",
"\nNOTICE\nRole: You are a professional software engineer, and your main task is to review the 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-----\n# System Design\n```text\n\n{\"Implementation approach\": \"To develop this snake game, we will use the Python language and choose the Pygame library. Pygame is an open-source Python module collection specifically designed for writing video games. It provides functionalities such as displaying images and playing sounds, making it suitable for creating intuitive and responsive user interfaces. We will ensure efficient game logic to prevent any delays during gameplay. The scoring system will be simple, with the snake gaining points for each food it eats. We will use Pygame's event handling system to implement pause and resume functionality, as well as high-score tracking. The difficulty will increase by speeding up the snake's movement. In the initial version, we will focus on single-player mode and consider adding multiplayer mode and customizable skins in future updates. Based on the new requirement, we will also add a moving obstacle that appears randomly. If the snake eats this obstacle, the game will end. If the snake does not eat the obstacle, it will disappear after 5 seconds. For this, we need to add mechanisms for obstacle generation, movement, and disappearance in the game logic.\", \"Project_name\": \"snake_game\", \"File list\": [\"main.py\", \"game.py\", \"snake.py\", \"food.py\", \"obstacle.py\", \"scoreboard.py\", \"constants.py\", \"assets/styles.css\", \"assets/index.html\"], \"Data structures and interfaces\": \"```mermaid\n classDiagram\n class Game{\n +int score\n +int speed\n +bool game_over\n +bool paused\n +Snake snake\n +Food food\n +Obstacle obstacle\n +Scoreboard scoreboard\n +start_game() void\n +pause_game() void\n +resume_game() void\n +end_game() void\n +increase_difficulty() void\n +update() void\n +render() void\n Game()\n }\n class Snake{\n +list body_parts\n +str direction\n +bool grow\n +move() void\n +grow() void\n +check_collision() bool\n Snake()\n }\n class Food{\n +tuple position\n +spawn() void\n Food()\n }\n class Obstacle{\n +tuple position\n +int lifetime\n +bool active\n +spawn() void\n +move() void\n +check_collision() bool\n +disappear() void\n Obstacle()\n }\n class Scoreboard{\n +int high_score\n +update_score(int) void\n +reset_score() void\n +load_high_score() void\n +save_high_score() void\n Scoreboard()\n }\n class Constants{\n }\n Game \"1\" -- \"1\" Snake: has\n Game \"1\" -- \"1\" Food: has\n Game \"1\" -- \"1\" Obstacle: has\n Game \"1\" -- \"1\" Scoreboard: has\n ```\", \"Program call flow\": \"```sequenceDiagram\n participant M as Main\n participant G as Game\n participant S as Snake\n participant F as Food\n participant O as Obstacle\n participant SB as Scoreboard\n M->>G: start_game()\n loop game loop\n G->>S: move()\n G->>S: check_collision()\n G->>F: spawn()\n G->>O: spawn()\n G->>O: move()\n G->>O: check_collision()\n G->>O: disappear()\n G->>SB: update_score(score)\n G->>G: update()\n G->>G: render()\n alt if paused\n M->>G: pause_game()\n M->>G: resume_game()\n end\n alt if game_over\n G->>M: end_game()\n end\n end\n```\", \"Anything UNCLEAR\": \"There is no need for further clarification as the requirements are already clear.\"}\n\n```\n-----\n# Tasks\n```text\n\n{\"Required Python third-party packages\": [\"pygame==2.0.1\"], \"Required Other language third-party packages\": [\"No third-party packages required for other languages.\"], \"Full API spec\": \"\n openapi: 3.0.0\n info:\n title: Snake Game API\n version: \"1.0.0\"\n paths:\n /start:\n get:\n summary: Start the game\n responses:\n '200':\n description: Game started successfully\n /pause:\n get:\n summary: Pause the game\n responses:\n '200':\n description: Game paused successfully\n /resume:\n get:\n summary: Resume the game\n responses:\n '200':\n description: Game resumed successfully\n /end:\n get:\n summary: End the game\n responses:\n '200':\n description: Game ended successfully\n /score:\n get:\n summary: Get the current score\n responses:\n '200':\n description: Current score retrieved successfully\n /highscore:\n get:\n summary: Get the high score\n responses:\n '200':\n description: High score retrieved successfully\n components: {}\n \", \"Logic Analysis\": [[\"constants.py\", \"Contains all the constant values like screen size, colors, game speeds, etc. This should be implemented first as it provides the base values for other components.\"], [\"snake.py\", \"Contains the Snake class with methods for movement, growth, and collision detection. It is dependent on constants.py for configuration values.\"], [\"food.py\", \"Contains the Food class responsible for spawning food items on the screen. It is dependent on constants.py for configuration values.\"], [\"obstacle.py\", \"Contains the Obstacle class with methods for spawning, moving, and disappearing of obstacles, as well as collision detection with the snake. It is dependent on constants.py for configuration values.\"], [\"scoreboard.py\", \"Contains the Scoreboard class for updating, resetting, loading, and saving high scores. It may use constants.py for configuration values and depends on the game's scoring logic.\"], [\"game.py\", \"Contains the main Game class which includes the game loop and methods for starting, pausing, resuming, and ending the game. It is dependent on snake.py, food.py, obstacle.py, and scoreboard.py.\"], [\"main.py\", \"The entry point of the game that initializes the game and starts the game loop. It is dependent on game.py.\"]], \"Task list\": [\"constants.py\", \"snake.py\", \"food.py\", \"obstacle.py\", \"scoreboard.py\", \"game.py\", \"main.py\"], \"Shared Knowledge\": \"\n 'constants.py' should contain all the necessary configurations for the game, such as screen dimensions, color definitions, and speed settings. These constants will be used across multiple files, ensuring consistency and ease of updates. Ensure that the Pygame library is initialized correctly in 'main.py' before starting the game loop. Also, make sure that the game's state is managed properly when pausing and resuming the game.\n \", \"Anything UNCLEAR\": \"The interaction between the 'obstacle.py' and the game loop needs to be clearly defined to ensure obstacles appear and disappear correctly. The lifetime of the obstacle and its random movement should be implemented in a way that does not interfere with the game's performance.\"}\n\n```\n-----\n```python\n\n## game.py\nimport pygame\nfrom snake import Snake\nfrom food import Food\n\nclass Game:\n def __init__(self):\n self.score = 0\n self.level = 1\n self.snake = Snake()\n self.food = Food()\n\n def start_game(self):\n pygame.init()\n self.initialize_game()\n self.game_loop()\n\n def initialize_game(self):\n self.score = 0\n self.level = 1\n self.snake.reset()\n self.food.generate()\n\n def game_loop(self):\n game_over = False\n\n while not game_over:\n self.update()\n self.draw()\n self.handle_events()\n self.check_collision()\n self.increase_score()\n self.increase_level()\n\n if self.snake.is_collision():\n game_over = True\n self.game_over()\n\n def update(self):\n self.snake.move()\n\n def draw(self):\n self.snake.draw()\n self.food.draw()\n\n def handle_events(self):\n for event in pygame.event.get():\n if event.type == pygame.QUIT:\n pygame.quit()\n quit()\n elif event.type == pygame.KEYDOWN:\n if event.key == pygame.K_UP:\n self.snake.change_direction(\"UP\")\n elif event.key == pygame.K_DOWN:\n self.snake.change_direction(\"DOWN\")\n elif event.key == pygame.K_LEFT:\n self.snake.change_direction(\"LEFT\")\n elif event.key == pygame.K_RIGHT:\n self.snake.change_direction(\"RIGHT\")\n\n def check_collision(self):\n if self.snake.get_head() == self.food.get_position():\n self.snake.grow()\n self.food.generate()\n\n def increase_score(self):\n self.score += 1\n\n def increase_level(self):\n if self.score % 10 == 0:\n self.level += 1\n\n def game_over(self):\n print(\"Game Over\")\n self.initialize_game()\n\n\n```\n-----\n```python\n\n## snake.py\nimport pygame\n\nclass Snake:\n def __init__(self):\n self.body = [(0, 0)]\n self.direction = (1, 0)\n\n def move(self):\n head = self.body[0]\n dx, dy = self.direction\n new_head = (head[0] + dx, head[1] + dy)\n self.body.insert(0, new_head)\n self.body.pop()\n\n def change_direction(self, direction):\n if direction == \"UP\":\n self.direction = (0, -1)\n elif direction == \"DOWN\":\n self.direction = (0, 1)\n elif direction == \"LEFT\":\n self.direction = (-1, 0)\n elif direction == \"RIGHT\":\n self.direction = (1, 0)\n\n def grow(self):\n tail = self.body[-1]\n dx, dy = self.direction\n new_tail = (tail[0] - dx, tail[1] - dy)\n self.body.append(new_tail)\n\n def get_head(self):\n return self.body[0]\n\n def get_body(self):\n return self.body[1:]\n\n\n```\n-----\n```python\n\n## main.py\nimport pygame\nfrom game import Game\n\ndef main():\n pygame.init()\n game = Game()\n game.start_game()\n\nif __name__ == \"__main__\":\n main()\n\n\n```\n-----\n```python\n\n## food.py\nimport random\n\nclass Food:\n def __init__(self):\n self.position = (0, 0)\n\n def generate(self):\n x = random.randint(0, 9)\n y = random.randint(0, 9)\n self.position = (x, y)\n\n def get_position(self):\n return self.position\n\n\n```\n-----\n\n## Code Review All: Please read all historical files and find possible bugs in the files, such as unimplemented functions, calling errors, unreferences, etc.\n\n## Call flow: mermaid code, based on the implemented function, use mermaid to draw a complete call chain\n\n## Summary: Summary based on the implementation of historical files\n\n## TODOs: Python dict[str, str], write down the list of files that need to be modified and the reasons. We will modify them later.\n\n": "## Code Review\n\n### game.py\n- The `game_over` variable is not being used in the `game_loop` method. It can be removed.\n- The `increase_score` method should only increase the score if there is a collision with the food. Move the score increment logic inside the `check_collision` method.\n- The `increase_level` method should only increase the level if the score is divisible by 10. Move the level increment logic inside the `check_collision` method.\n- The `game_over` method should be called after the game loop ends, not inside the loop.\n\n### snake.py\n- The `get_body` method is not being used. It can be removed.\n\n### main.py\n- The `pygame.init()` method is being called twice, once in the `main` function and once in the `start_game` method of the `Game` class. It should only be called once, preferably in the `main` function.\n\n### food.py\n- The `generate` method should generate random positions within the game boundaries defined in the `constants.py` file.\n\n## Call Flow\n\n```mermaid\nsequenceDiagram\n participant M as Main\n participant G as Game\n participant S as Snake\n participant F as Food\n\n M->>G: start_game()\n G->>G: initialize_game()\n G->>G: game_loop()\n loop game loop\n G->>S: move()\n G->>G: update()\n G->>S: draw()\n G->>F: draw()\n G->>G: handle_events()\n G->>G: check_collision()\n G->>G: increase_score()\n G->>G: increase_level()\n alt if snake collision\n G->>G: game_over()\n end\n end\n```\n\n## Summary\nThe code consists of several classes that work together to create a snake game. The `Game` class manages the game loop and handles events. The `Snake` class represents the snake and handles its movement and collision detection. The `Food` class represents the food and handles its generation and position. The code is missing some functionality, such as updating the score and level, and handling game over conditions. There are also some minor issues with the code structure and logic that need to be addressed.\n\n## TODOs\n- Modify `game.py`:\n - Remove the unused `game_over` variable in the `game_loop` method.\n - Move the score increment logic from the `increase_score` method to the `check_collision` method.\n - Move the level increment logic from the `increase_level` method to the `check_collision` method.\n - Call the `game_over` method after the game loop ends.\n- Modify `snake.py`:\n - Remove the unused `get_body` method.\n- Modify `main.py`:\n - Remove the redundant `pygame.init()` method call in the `start_game` method of the `Game` class.\n- Modify `food.py`:\n - Generate random positions within the game boundaries defined in the `constants.py` file.",
"\nNOTICE\nRole: You are a professional software engineer, and your main task is to review the 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-----\n# System Design\n```text\n\n{\"Implementation approach\": \"To develop this snake game, we will use the Python language and choose the Pygame library. Pygame is an open-source Python module collection specifically designed for writing video games. It provides functionalities such as displaying images and playing sounds, making it suitable for creating intuitive and responsive user interfaces. We will ensure efficient game logic to prevent any delays during gameplay. The scoring system will be simple, with the snake gaining points for each food it eats. We will use Pygame's event handling system to implement pause and resume functionality, as well as high-score tracking. The difficulty will increase by speeding up the snake's movement. In the initial version, we will focus on single-player mode and consider adding multiplayer mode and customizable skins in future updates. Based on the new requirement, we will also add a moving obstacle that appears randomly. If the snake eats this obstacle, the game will end. If the snake does not eat the obstacle, it will disappear after 5 seconds. For this, we need to add mechanisms for obstacle generation, movement, and disappearance in the game logic.\", \"Project_name\": \"snake_game\", \"File list\": [\"main.py\", \"game.py\", \"snake.py\", \"food.py\", \"obstacle.py\", \"scoreboard.py\", \"constants.py\", \"assets/styles.css\", \"assets/index.html\"], \"Data structures and interfaces\": \"```mermaid\n classDiagram\n class Game{\n +int score\n +int speed\n +bool game_over\n +bool paused\n +Snake snake\n +Food food\n +Obstacle obstacle\n +Scoreboard scoreboard\n +start_game() void\n +pause_game() void\n +resume_game() void\n +end_game() void\n +increase_difficulty() void\n +update() void\n +render() void\n Game()\n }\n class Snake{\n +list body_parts\n +str direction\n +bool grow\n +move() void\n +grow() void\n +check_collision() bool\n Snake()\n }\n class Food{\n +tuple position\n +spawn() void\n Food()\n }\n class Obstacle{\n +tuple position\n +int lifetime\n +bool active\n +spawn() void\n +move() void\n +check_collision() bool\n +disappear() void\n Obstacle()\n }\n class Scoreboard{\n +int high_score\n +update_score(int) void\n +reset_score() void\n +load_high_score() void\n +save_high_score() void\n Scoreboard()\n }\n class Constants{\n }\n Game \"1\" -- \"1\" Snake: has\n Game \"1\" -- \"1\" Food: has\n Game \"1\" -- \"1\" Obstacle: has\n Game \"1\" -- \"1\" Scoreboard: has\n ```\", \"Program call flow\": \"```sequenceDiagram\n participant M as Main\n participant G as Game\n participant S as Snake\n participant F as Food\n participant O as Obstacle\n participant SB as Scoreboard\n M->>G: start_game()\n loop game loop\n G->>S: move()\n G->>S: check_collision()\n G->>F: spawn()\n G->>O: spawn()\n G->>O: move()\n G->>O: check_collision()\n G->>O: disappear()\n G->>SB: update_score(score)\n G->>G: update()\n G->>G: render()\n alt if paused\n M->>G: pause_game()\n M->>G: resume_game()\n end\n alt if game_over\n G->>M: end_game()\n end\n end\n```\", \"Anything UNCLEAR\": \"There is no need for further clarification as the requirements are already clear.\"}\n\n```\n-----\n# Tasks\n```text\n\n{\"Required Python third-party packages\": [\"pygame==2.0.1\"], \"Required Other language third-party packages\": [\"No third-party packages required for other languages.\"], \"Full API spec\": \"\n openapi: 3.0.0\n info:\n title: Snake Game API\n version: \"1.0.0\"\n paths:\n /start:\n get:\n summary: Start the game\n responses:\n '200':\n description: Game started successfully\n /pause:\n get:\n summary: Pause the game\n responses:\n '200':\n description: Game paused successfully\n /resume:\n get:\n summary: Resume the game\n responses:\n '200':\n description: Game resumed successfully\n /end:\n get:\n summary: End the game\n responses:\n '200':\n description: Game ended successfully\n /score:\n get:\n summary: Get the current score\n responses:\n '200':\n description: Current score retrieved successfully\n /highscore:\n get:\n summary: Get the high score\n responses:\n '200':\n description: High score retrieved successfully\n components: {}\n \", \"Logic Analysis\": [[\"constants.py\", \"Contains all the constant values like screen size, colors, game speeds, etc. This should be implemented first as it provides the base values for other components.\"], [\"snake.py\", \"Contains the Snake class with methods for movement, growth, and collision detection. It is dependent on constants.py for configuration values.\"], [\"food.py\", \"Contains the Food class responsible for spawning food items on the screen. It is dependent on constants.py for configuration values.\"], [\"obstacle.py\", \"Contains the Obstacle class with methods for spawning, moving, and disappearing of obstacles, as well as collision detection with the snake. It is dependent on constants.py for configuration values.\"], [\"scoreboard.py\", \"Contains the Scoreboard class for updating, resetting, loading, and saving high scores. It may use constants.py for configuration values and depends on the game's scoring logic.\"], [\"game.py\", \"Contains the main Game class which includes the game loop and methods for starting, pausing, resuming, and ending the game. It is dependent on snake.py, food.py, obstacle.py, and scoreboard.py.\"], [\"main.py\", \"The entry point of the game that initializes the game and starts the game loop. It is dependent on game.py.\"]], \"Task list\": [\"constants.py\", \"snake.py\", \"food.py\", \"obstacle.py\", \"scoreboard.py\", \"game.py\", \"main.py\"], \"Shared Knowledge\": \"\n 'constants.py' should contain all the necessary configurations for the game, such as screen dimensions, color definitions, and speed settings. These constants will be used across multiple files, ensuring consistency and ease of updates. Ensure that the Pygame library is initialized correctly in 'main.py' before starting the game loop. Also, make sure that the game's state is managed properly when pausing and resuming the game.\n \", \"Anything UNCLEAR\": \"The interaction between the 'obstacle.py' and the game loop needs to be clearly defined to ensure obstacles appear and disappear correctly. The lifetime of the obstacle and its random movement should be implemented in a way that does not interfere with the game's performance.\"}\n\n```\n-----\n```python\n\n## food.py\nimport random\n\nclass Food:\n def __init__(self):\n self.position = (0, 0)\n\n def generate(self):\n x = random.randint(0, 9)\n y = random.randint(0, 9)\n self.position = (x, y)\n\n def get_position(self):\n return self.position\n\n\n```\n-----\n```python\n\n## snake.py\nimport pygame\n\nclass Snake:\n def __init__(self):\n self.body = [(0, 0)]\n self.direction = (1, 0)\n\n def move(self):\n head = self.body[0]\n dx, dy = self.direction\n new_head = (head[0] + dx, head[1] + dy)\n self.body.insert(0, new_head)\n self.body.pop()\n\n def change_direction(self, direction):\n if direction == \"UP\":\n self.direction = (0, -1)\n elif direction == \"DOWN\":\n self.direction = (0, 1)\n elif direction == \"LEFT\":\n self.direction = (-1, 0)\n elif direction == \"RIGHT\":\n self.direction = (1, 0)\n\n def grow(self):\n tail = self.body[-1]\n dx, dy = self.direction\n new_tail = (tail[0] - dx, tail[1] - dy)\n self.body.append(new_tail)\n\n def get_head(self):\n return self.body[0]\n\n def get_body(self):\n return self.body[1:]\n\n\n```\n-----\n```python\n\n## game.py\nimport pygame\nfrom snake import Snake\nfrom food import Food\n\nclass Game:\n def __init__(self):\n self.score = 0\n self.level = 1\n self.snake = Snake()\n self.food = Food()\n\n def start_game(self):\n pygame.init()\n self.initialize_game()\n self.game_loop()\n\n def initialize_game(self):\n self.score = 0\n self.level = 1\n self.snake.reset()\n self.food.generate()\n\n def game_loop(self):\n game_over = False\n\n while not game_over:\n self.update()\n self.draw()\n self.handle_events()\n self.check_collision()\n self.increase_score()\n self.increase_level()\n\n if self.snake.is_collision():\n game_over = True\n self.game_over()\n\n def update(self):\n self.snake.move()\n\n def draw(self):\n self.snake.draw()\n self.food.draw()\n\n def handle_events(self):\n for event in pygame.event.get():\n if event.type == pygame.QUIT:\n pygame.quit()\n quit()\n elif event.type == pygame.KEYDOWN:\n if event.key == pygame.K_UP:\n self.snake.change_direction(\"UP\")\n elif event.key == pygame.K_DOWN:\n self.snake.change_direction(\"DOWN\")\n elif event.key == pygame.K_LEFT:\n self.snake.change_direction(\"LEFT\")\n elif event.key == pygame.K_RIGHT:\n self.snake.change_direction(\"RIGHT\")\n\n def check_collision(self):\n if self.snake.get_head() == self.food.get_position():\n self.snake.grow()\n self.food.generate()\n\n def increase_score(self):\n self.score += 1\n\n def increase_level(self):\n if self.score % 10 == 0:\n self.level += 1\n\n def game_over(self):\n print(\"Game Over\")\n self.initialize_game()\n\n\n```\n-----\n```python\n\n## main.py\nimport pygame\nfrom game import Game\n\ndef main():\n pygame.init()\n game = Game()\n game.start_game()\n\nif __name__ == \"__main__\":\n main()\n\n\n```\n-----\n\n## Code Review All: Please read all historical files and find possible bugs in the files, such as unimplemented functions, calling errors, unreferences, etc.\n\n## Call flow: mermaid code, based on the implemented function, use mermaid to draw a complete call chain\n\n## Summary: Summary based on the implementation of historical files\n\n## TODOs: Python dict[str, str], write down the list of files that need to be modified and the reasons. We will modify them later.\n\n": "## Code Review\n\n### food.py\n- The `generate` method should be renamed to `spawn` to be consistent with the naming convention used in other classes.\n- The `get_position` method can be simplified by directly returning the `self.position` attribute.\n\n### snake.py\n- The `change_direction` method should handle lowercase direction inputs as well to make it more flexible.\n- The `grow` method can be optimized by directly appending the new tail position to the `self.body` list instead of calculating it based on the current direction.\n\n### game.py\n- The `initialize_game` method should call the `reset` method of the `self.snake` object instead of a non-existent `reset` method.\n- The `game_over` method should reset the game state by calling the `initialize_game` method instead of manually resetting the score and level attributes.\n- The `draw` method should be implemented to draw the snake and food on the screen using the Pygame library.\n- The `handle_events` method should handle the case when the snake is moving in one direction and the opposite direction key is pressed, which should be ignored to prevent the snake from reversing.\n- The `check_collision` method should be implemented to check for collisions between the snake's head and the food, and handle the snake's growth and food respawn accordingly.\n- The `increase_score` and `increase_level` methods should be implemented to increase the score and level based on the game's scoring logic.\n- The `game_over` method should be implemented to handle the game over condition, display a game over message, and reset the game state.\n\n### main.py\n- The `pygame.init()` method is called twice, once in the `main` function and once in the `Game` class. It should only be called once, preferably in the `main` function.\n\n## Call Flow\n\n```mermaid\nsequenceDiagram\n participant M as Main\n participant G as Game\n participant S as Snake\n participant F as Food\n\n M->>G: start_game()\n loop game loop\n G->>S: move()\n G->>S: check_collision()\n G->>F: spawn()\n G->>G: update()\n G->>G: draw()\n G->>G: handle_events()\n G->>G: check_collision()\n G->>G: increase_score()\n G->>G: increase_level()\n alt if game over\n G->>G: game_over()\n end\n end\n```\n\n## Summary\n\nThe code consists of several classes that represent different components of the snake game, such as the snake itself, the food, and the game logic. The `main` function initializes the game and starts the game loop. The game loop handles updating the game state, drawing the game objects on the screen, and handling user input. The snake can move in different directions, grow when it eats food, and collide with itself or the boundaries of the screen. The game keeps track of the score and level, and ends when the snake collides with itself or the boundaries.\n\n## TODOs\n\n- Modify `food.py`:\n - Rename the `generate` method to `spawn`.\n - Simplify the `get_position` method.\n- Modify `snake.py`:\n - Handle lowercase direction inputs in the `change_direction` method.\n - Optimize the `grow` method by directly appending the new tail position to the `self.body` list.\n- Modify `game.py`:\n - Call the `reset` method of the `self.snake` object in the `initialize_game` method.\n - Implement the `draw` method to draw the snake and food on the screen.\n - Implement the `handle_events` method to handle direction changes and ignore opposite direction inputs.\n - Implement the `check_collision` method to handle collisions between the snake's head and the food.\n - Implement the `increase_score` and `increase_level` methods based on the game's scoring logic.\n - Implement the `game_over` method to handle the game over condition and reset the game state.\n- Remove the duplicate `pygame.init()` call in the `main` function.",
@@ -53,7 +53,7 @@
"You are mathematician.#MSG_SEP#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.#MSG_SEP#If the information is insufficient, you can search in the context or knowledge.#MSG_SEP#Answer the following questions strictly in Chinese, and the answers must follow the Markdown format.#SYSTEM_MSG_END#Does Susie have an apple?": "根据提供的信息,Susie曾经有一个苹果,但是她已经吃掉了。",
"\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设计一个名为'add'的函数,该函数接受两个整数作为输入,并返回它们的和。\n\n## Tasks\n\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: \n```python\n## \n...\n```\n\n# Instruction: Based on the context, follow \"Format example\", write code.\n\n## Code: . 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": "## Code:\n\n```python\ndef add(num1: int, num2: int) -> int:\n \"\"\"\n This function takes two integers as input and returns their sum.\n \n Args:\n num1 (int): The first integer.\n num2 (int): The second integer.\n \n Returns:\n int: The sum of the two integers.\n \"\"\"\n return num1 + num2\n```\n\n# Instruction: Based on the context, write code.\n\n## Code: \n```python\ndef add(num1: int, num2: int) -> int:\n \"\"\"\n This function takes two integers as input and returns their sum.\n \n Args:\n num1 (int): The first integer.\n num2 (int): The second integer.\n \n Returns:\n int: The sum of the two integers.\n \"\"\"\n return num1 + num2\n```",
"\n你是一个工程师。下面是背景信息与你的当前任务,请为任务撰写代码。\n撰写的代码应该符合PEP8,优雅,模块化,易于阅读与维护,代码本身应该有__main__入口来防止桩函数\n\n## 用户编写程序所需的全部、详尽的文件路径列表(只需要相对路径,并不需要前缀,组织形式应该符合PEP规范)\n\n- `main.py`: 主程序文件\n- `search_engine.py`: 搜索引擎实现文件\n- `knowledge_base.py`: 知识库管理文件\n- `user_interface.py`: 用户界面文件\n- `data_import.py`: 数据导入功能文件\n- `data_export.py`: 数据导出功能文件\n- `utils.py`: 工具函数文件\n\n## 数据结构\n\n- `KnowledgeBase`: 知识库类,用于管理私有知识库的内容、分类、标签和关键词。\n- `SearchEngine`: 搜索引擎类,基于大语言模型,用于对用户输入的关键词或短语进行语义理解,并提供准确的搜索结果。\n- `SearchResult`: 搜索结果类,包含与用户搜索意图相关的知识库内容的相关信息。\n- `UserInterface`: 用户界面类,提供简洁、直观的用户界面,支持多种搜索方式和搜索结果的排序和过滤。\n- `DataImporter`: 数据导入类,支持多种数据格式的导入功能,用于将外部数据导入到知识库中。\n- `DataExporter`: 数据导出类,支持多种数据格式的导出功能,用于将知识库内容进行备份和分享。\n\n## API接口\n\n- `KnowledgeBase`类接口:\n - `add_entry(entry: str, category: str, tags: List[str], keywords: List[str]) -> bool`: 添加知识库条目。\n - `delete_entry(entry_id: str) -> bool`: 删除知识库条目。\n - `update_entry(entry_id: str, entry: str, category: str, tags: List[str], keywords: List[str]) -> bool`: 更新知识库条目。\n - `search_entries(query: str) -> List[str]`: 根据查询词搜索知识库条目。\n\n- `SearchEngine`类接口:\n - `search(query: str) -> SearchResult`: 根据用户查询词进行搜索,返回与查询意图相关的搜索结果。\n\n- `UserInterface`类接口:\n - `display_search_results(results: List[SearchResult]) -> None`: 显示搜索结果。\n - `filter_results(results: List[SearchResult], filters: Dict[str, Any]) -> List[SearchResult]`: 根据过滤条件对搜索结果进行过滤。\n - `sort_results(results: List[SearchResult], key: str, reverse: bool = False) -> List[SearchResult]`: 根据指定的键对搜索结果进行排序。\n\n- `DataImporter`类接口:\n - `import_data(file_path: str) -> bool`: 导入外部数据到知识库。\n\n- `DataExporter`类接口:\n - `export_data(file_path: str) -> bool`: 导出知识库数据到外部文件。\n\n## 调用流程(以dot语言描述)\n\n```dot\ndigraph call_flow {\n rankdir=LR;\n\n subgraph cluster_user_program {\n label=\"User Program\";\n style=dotted;\n\n main_py -> search_engine_py;\n main_py -> knowledge_base_py;\n main_py -> user_interface_py;\n main_py -> data_import_py;\n main_py -> data_export_py;\n\n search_engine_py -> knowledge_base_py;\n search_engine_py -> user_interface_py;\n\n user_interface_py -> knowledge_base_py;\n user_interface_py -> search_engine_py;\n\n data_import_py -> knowledge_base_py;\n data_import_py -> user_interface_py;\n\n data_export_py -> knowledge_base_py;\n data_export_py -> user_interface_py;\n }\n\n main_py [label=\"main.py\"];\n search_engine_py [label=\"search_engine.py\"];\n knowledge_base_py [label=\"knowledge_base.py\"];\n user_interface_py [label=\"user_interface.py\"];\n data_import_py [label=\"data_import.py\"];\n data_export_py [label=\"data_export.py\"];\n}\n```\n\n这是一个简化的调用流程图,展示了各个模块之间的调用关系。用户程序的`main.py`文件通过调用其他模块实现搜索引擎的功能。`search_engine.py`模块与`knowledge_base.py`和`user_interface.py`模块进行交互,实现搜索算法和搜索结果的展示。`data_import.py`和`data_export.py`模块与`knowledge_base.py`和`user_interface.py`模块进行交互,实现数据导入和导出的功能。用户界面模块`user_interface.py`与其他模块进行交互,提供简洁、直观的用户界面,并支持搜索方式、排序和过滤等操作。\n\n## 当前任务\n\n\n完成main.py的功能": "根据任务要求,我们需要在`main.py`文件中完成以下功能:\n\n1. 导入所需模块和类:`KnowledgeBase`、`SearchEngine`、`UserInterface`、`DataImporter`、`DataExporter`。\n2. 创建一个`KnowledgeBase`对象,用于管理知识库的内容。\n3. 创建一个`SearchEngine`对象,用于进行搜索操作。\n4. 创建一个`UserInterface`对象,用于与用户进行交互。\n5. 创建一个`DataImporter`对象,用于导入外部数据到知识库。\n6. 创建一个`DataExporter`对象,用于导出知识库数据到外部文件。\n7. 实现主程序逻辑,包括用户界面的显示和用户输入的处理。\n\n下面是一个示例的`main.py`代码实现:\n\n```python\nfrom knowledge_base import KnowledgeBase\nfrom search_engine import SearchEngine\nfrom user_interface import UserInterface\nfrom data_import import DataImporter\nfrom data_export import DataExporter\n\ndef main():\n # 创建知识库对象\n knowledge_base = KnowledgeBase()\n\n # 创建搜索引擎对象\n search_engine = SearchEngine(knowledge_base)\n\n # 创建用户界面对象\n user_interface = UserInterface(search_engine)\n\n # 创建数据导入对象\n data_importer = DataImporter(knowledge_base)\n\n # 创建数据导出对象\n data_exporter = DataExporter(knowledge_base)\n\n # 主程序逻辑\n while True:\n # 显示用户界面\n user_interface.display_menu()\n\n # 获取用户输入\n user_input = input(\"请输入选项:\")\n\n # 处理用户输入\n if user_input == \"1\":\n # 搜索知识库\n query = input(\"请输入搜索关键词:\")\n search_results = search_engine.search(query)\n user_interface.display_search_results(search_results)\n elif user_input == \"2\":\n # 导入数据\n file_path = input(\"请输入要导入的文件路径:\")\n data_importer.import_data(file_path)\n elif user_input == \"3\":\n # 导出数据\n file_path = input(\"请输入要导出的文件路径:\")\n data_exporter.export_data(file_path)\n elif user_input == \"4\":\n # 退出程序\n break\n else:\n print(\"无效的选项,请重新输入。\")\n\nif __name__ == \"__main__\":\n main()\n```\n\n在这个示例代码中,我们首先导入了所需的模块和类。然后,我们创建了`KnowledgeBase`、`SearchEngine`、`UserInterface`、`DataImporter`和`DataExporter`对象。接下来,我们使用一个无限循环来实现主程序逻辑。在每次循环中,我们首先显示用户界面,然后获取用户输入,并根据用户输入执行相应的操作。用户可以选择搜索知识库、导入数据、导出数据或退出程序。最后,我们使用`if __name__ == \"__main__\":`来确保代码在作为主程序运行时才会执行。",
- "\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 the Pygame library to create the game interface and handle user input. The game logic will be implemented using Python classes and data structures.\", \"File list\": [\"main.py\", \"game.py\"], \"Data structures and interfaces\": \"classDiagram\\n class Game {\\n -grid: List[List[int]]\\n -score: int\\n -game_over: bool\\n +__init__()\\n +reset_game()\\n +move(direction: str)\\n +is_game_over() bool\\n +get_empty_cells() List[Tuple[int, int]]\\n +add_new_tile()\\n +get_score() int\\n }\\n class UI {\\n -game: Game\\n +__init__(game: Game)\\n +draw_grid()\\n +draw_score()\\n +draw_game_over()\\n +handle_input()\\n }\\n Game --> UI\", \"Program call flow\": \"sequenceDiagram\\n participant M as Main\\n participant G as Game\\n participant U as UI\\n M->>G: reset_game()\\n M->>U: draw_grid()\\n M->>U: draw_score()\\n M->>U: handle_input()\\n U->>G: move(direction)\\n G->>G: add_new_tile()\\n G->>U: draw_grid()\\n G->>U: draw_score()\\n G->>U: draw_game_over()\\n G->>G: is_game_over()\\n G->>G: get_empty_cells()\\n G->>G: get_score()\", \"Anything UNCLEAR\": \"...\"}\n\n## Tasks\n{\"Required Python packages\": [\"pygame==2.0.1\"], \"Required Other language third-party packages\": [\"No third-party dependencies required\"], \"Logic Analysis\": [[\"game.py\", \"Contains Game class and related functions for game logic\"], [\"main.py\", \"Contains main function, initializes the game and UI\"]], \"Task list\": [\"game.py\", \"main.py\"], \"Full API spec\": \"\", \"Shared Knowledge\": \"The game logic will be implemented using Python classes and data structures. The Pygame library will be used to create the game interface and handle user input.\", \"Anything UNCLEAR\": \"...\"}\n\n## Legacy Code\n```Code\n----- main.py\nif __name__ == \"__main__\":\nmain()\n```\n\n## Debug logs\n```text\nE.......F\n======================================================================\nERROR: test_add_new_tile (__main__.TestGame)\n----------------------------------------------------------------------\nTraceback (most recent call last):\n File \"/Users/xx/tests/test_game.py\", line 104, in test_add_new_tile\n self.assertIn(self.game.grid[empty_cells[0][0]][empty_cells[0][1]], [2, 4])\nIndexError: list index out of range\n\n======================================================================\nFAIL: test_reset_game (__main__.TestGame)\n----------------------------------------------------------------------\nTraceback (most recent call last):\n File \"/Users/xx/tests/test_game.py\", line 13, in test_reset_game\n self.assertEqual(self.game.grid, [[0 for _ in range(4)] for _ in range(4)])\nAssertionError: Lists differ: [[0, 0, 0, 0], [0, 2, 0, 0], [0, 0, 0, 2], [0, 0, 0, 0]] != [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]\n\nFirst differing element 1:\n[0, 2, 0, 0]\n[0, 0, 0, 0]\n\n- [[0, 0, 0, 0], [0, 2, 0, 0], [0, 0, 0, 2], [0, 0, 0, 0]]\n? --- ^\n\n+ [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]\n? +++ ^\n\n\n----------------------------------------------------------------------\nRan 9 tests in 0.002s\n\nFAILED (failures=1, errors=1)\n\n\n{\"design_filename\": \"docs/system_design/20231221155954.json\", \"task_filename\": \"docs/tasks/20231221155954.json\", \"codes_filenames\": [\"game.py\", \"main.py\"], \"reason\": \"```json\\n{\\n \\\"game.py\\\": \\\"Add handling for no empty cells in add_new_tile function, Update score in move function\\\",\\n \\\"main.py\\\": \\\"Handle game over condition in the game loop\\\"\\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": "## Code: game.py\n\n```python\nclass Game:\n def __init__(self):\n self.grid = [[0 for _ in range(4)] for _ in range(4)]\n self.score = 0\n self.game_over = False\n\n def reset_game(self):\n self.grid = [[0 for _ in range(4)] for _ in range(4)]\n self.score = 0\n self.game_over = False\n\n def move(self, direction: str):\n # TODO: Implement the move logic based on the given direction\n pass\n\n def is_game_over(self) -> bool:\n # TODO: Implement the game over logic\n pass\n\n def get_empty_cells(self) -> List[Tuple[int, int]]:\n # TODO: Return a list of coordinates of empty cells in the grid\n pass\n\n def add_new_tile(self):\n # TODO: Add a new tile (either 2 or 4) to a random empty cell in the grid\n pass\n\n def get_score(self) -> int:\n return self.score\n```\n\n## Explanation:\nIn the `Game` class, we have implemented the constructor `__init__` to initialize the grid, score, and game_over attributes. The `reset_game` method is used to reset the game by setting the grid, score, and game_over attributes to their initial values.\n\nThe `move` method is not implemented yet and will handle the logic for moving the tiles in the grid based on the given direction.\n\nThe `is_game_over` method is not implemented yet and will check if the game is over by checking if there are any possible moves left.\n\nThe `get_empty_cells` method is not implemented yet and will return a list of coordinates of empty cells in the grid.\n\nThe `add_new_tile` method is not implemented yet and will add a new tile (either 2 or 4) to a random empty cell in the grid.\n\nThe `get_score` method returns the current score of the game.",
+ "\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 the Pygame library to create the game interface and handle user input. The game logic will be implemented using Python classes and data structures.\", \"File list\": [\"main.py\", \"game.py\"], \"Data structures and interfaces\": \"classDiagram\\n class Game {\\n -grid: List[List[int]]\\n -score: int\\n -game_over: bool\\n +__init__()\\n +reset_game()\\n +move(direction: str)\\n +is_game_over() bool\\n +get_empty_cells() List[Tuple[int, int]]\\n +add_new_tile()\\n +get_score() int\\n }\\n class UI {\\n -game: Game\\n +__init__(game: Game)\\n +draw_grid()\\n +draw_score()\\n +draw_game_over()\\n +handle_input()\\n }\\n Game --> UI\", \"Program call flow\": \"sequenceDiagram\\n participant M as Main\\n participant G as Game\\n participant U as UI\\n M->>G: reset_game()\\n M->>U: draw_grid()\\n M->>U: draw_score()\\n M->>U: handle_input()\\n U->>G: move(direction)\\n G->>G: add_new_tile()\\n G->>U: draw_grid()\\n G->>U: draw_score()\\n G->>U: draw_game_over()\\n G->>G: is_game_over()\\n G->>G: get_empty_cells()\\n G->>G: get_score()\", \"Anything UNCLEAR\": \"...\"}\n\n## Tasks\n{\"Required packages\": [\"pygame==2.0.1\"], \"Required Other language third-party packages\": [\"No third-party dependencies required\"], \"Logic Analysis\": [[\"game.py\", \"Contains Game class and related functions for game logic\"], [\"main.py\", \"Contains main function, initializes the game and UI\"]], \"Task list\": [\"game.py\", \"main.py\"], \"Full API spec\": \"\", \"Shared Knowledge\": \"The game logic will be implemented using Python classes and data structures. The Pygame library will be used to create the game interface and handle user input.\", \"Anything UNCLEAR\": \"...\"}\n\n## Legacy Code\n```Code\n----- main.py\nif __name__ == \"__main__\":\nmain()\n```\n\n## Debug logs\n```text\nE.......F\n======================================================================\nERROR: test_add_new_tile (__main__.TestGame)\n----------------------------------------------------------------------\nTraceback (most recent call last):\n File \"/Users/xx/tests/test_game.py\", line 104, in test_add_new_tile\n self.assertIn(self.game.grid[empty_cells[0][0]][empty_cells[0][1]], [2, 4])\nIndexError: list index out of range\n\n======================================================================\nFAIL: test_reset_game (__main__.TestGame)\n----------------------------------------------------------------------\nTraceback (most recent call last):\n File \"/Users/xx/tests/test_game.py\", line 13, in test_reset_game\n self.assertEqual(self.game.grid, [[0 for _ in range(4)] for _ in range(4)])\nAssertionError: Lists differ: [[0, 0, 0, 0], [0, 2, 0, 0], [0, 0, 0, 2], [0, 0, 0, 0]] != [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]\n\nFirst differing element 1:\n[0, 2, 0, 0]\n[0, 0, 0, 0]\n\n- [[0, 0, 0, 0], [0, 2, 0, 0], [0, 0, 0, 2], [0, 0, 0, 0]]\n? --- ^\n\n+ [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]\n? +++ ^\n\n\n----------------------------------------------------------------------\nRan 9 tests in 0.002s\n\nFAILED (failures=1, errors=1)\n\n\n{\"design_filename\": \"docs/system_design/20231221155954.json\", \"task_filename\": \"docs/tasks/20231221155954.json\", \"codes_filenames\": [\"game.py\", \"main.py\"], \"reason\": \"```json\\n{\\n \\\"game.py\\\": \\\"Add handling for no empty cells in add_new_tile function, Update score in move function\\\",\\n \\\"main.py\\\": \\\"Handle game over condition in the game loop\\\"\\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": "## Code: game.py\n\n```python\nclass Game:\n def __init__(self):\n self.grid = [[0 for _ in range(4)] for _ in range(4)]\n self.score = 0\n self.game_over = False\n\n def reset_game(self):\n self.grid = [[0 for _ in range(4)] for _ in range(4)]\n self.score = 0\n self.game_over = False\n\n def move(self, direction: str):\n # TODO: Implement the move logic based on the given direction\n pass\n\n def is_game_over(self) -> bool:\n # TODO: Implement the game over logic\n pass\n\n def get_empty_cells(self) -> List[Tuple[int, int]]:\n # TODO: Return a list of coordinates of empty cells in the grid\n pass\n\n def add_new_tile(self):\n # TODO: Add a new tile (either 2 or 4) to a random empty cell in the grid\n pass\n\n def get_score(self) -> int:\n return self.score\n```\n\n## Explanation:\nIn the `Game` class, we have implemented the constructor `__init__` to initialize the grid, score, and game_over attributes. The `reset_game` method is used to reset the game by setting the grid, score, and game_over attributes to their initial values.\n\nThe `move` method is not implemented yet and will handle the logic for moving the tiles in the grid based on the given direction.\n\nThe `is_game_over` method is not implemented yet and will check if the game is over by checking if there are any possible moves left.\n\nThe `get_empty_cells` method is not implemented yet and will return a list of coordinates of empty cells in the grid.\n\nThe `add_new_tile` method is not implemented yet and will add a new tile (either 2 or 4) to a random empty cell in the grid.\n\nThe `get_score` method returns the current score of the game.",
"\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编写一个从a加b的函数,返回a+b\n\n## Tasks\n\n\n## Code Files\n\n\n\n## Code to be Reviewed: \n```Code\n\ndef add(a, b):\n return a + \n\n```\n\n\n\n# Format example 1\n## Code Review: \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: \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: \n1. No, the code is not implemented as per the requirements. The `add` function is missing the second parameter `b` in the return statement. To fix it, we need to include `b` in the return statement as well. The corrected code should be:\n```python\ndef add(a, b):\n return a + b\n```\n\n## Actions\n1. Implement the fix mentioned above.\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编写一个从a加b的函数,返回a+b\n\n## Tasks\n\n\n## Code Files\n\n\n\n## Code to be Reviewed: \n```Code\n\ndef add(a, b):\n return a + \n\n```\n\n## Code Review: \n1. No, the code is not implemented as per the requirements. The `add` function is missing the second parameter `b` in the return statement. To fix it, we need to include `b` in the return statement as well. The corrected code should be:\n```python\ndef add(a, b):\n return a + b\n```\n\n## Actions\n1. Implement the fix mentioned above.\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 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## \n...\n```\n": "```python\ndef add(a, b):\n return a + b\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编写一个从a加b的函数,返回a+b\n\n## Tasks\n\n\n## Code Files\n\n\n\n## Code to be Reviewed: \n```Code\ndef add(a, b):\n return a + b\n\n```\n\n\n\n# Format example 1\n## Code Review: \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: \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: \n1. Yes.\n2. Yes.\n3. Yes.\n4. Yes.\n5. Yes.\n6. Yes.\n\n## Actions\npass\n\n## Code Review Result\nLGTM",
@@ -158,13 +158,13 @@
"\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{\"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 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 packages: typing.List[str] # Provide Required 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 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 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 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 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 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 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 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]",
@@ -201,20 +201,20 @@
"\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: # 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 \"Implementation approach\": \"We will use the Pygame library to create the game and handle user input. The game logic will be implemented using Python classes and functions.\",\n \"File list\": [\n \"main.py\",\n \"game.py\"\n ],\n \"Data structures and interfaces\": \"\\nclassDiagram\\n class Game {\\n -snake: list\\n -food: tuple\\n -score: int\\n +__init__()\\n +update()\\n +draw()\\n +handle_input()\\n +restart()\\n }\\n class Snake {\\n -body: list\\n -direction: str\\n +__init__(start_pos: tuple, length: int, direction: str)\\n +move()\\n +change_direction(new_direction: str)\\n +grow()\\n }\\n Game --> Snake\\n\",\n \"Program call flow\": \"\\nsequenceDiagram\\n participant M as Main\\n participant G as Game\\n M->>G: initialize game\\n G->>G: update game state\\n G->>G: draw game\\n G->>G: handle user input\\n G->>G: restart game\\n\",\n \"Anything UNCLEAR\": \"Clarification needed on the scoring system and how the game over condition will be handled.\"\n}\n[/CONTENT]",
- "\n## context\n{\"Implementation approach\":\"We will use the Pygame library to create the game and handle user input. The game logic will be implemented using Python classes and functions.\",\"File list\":[\"main.py\",\"game.py\"],\"Data structures and interfaces\":\"\\nclassDiagram\\n class Game {\\n -snake: list\\n -food: tuple\\n -score: int\\n +__init__()\\n +update()\\n +draw()\\n +handle_input()\\n +restart()\\n }\\n class Snake {\\n -body: list\\n -direction: str\\n +__init__(start_pos: tuple, length: int, direction: str)\\n +move()\\n +change_direction(new_direction: str)\\n +grow()\\n }\\n Game --> Snake\\n\",\"Program call flow\":\"\\nsequenceDiagram\\n participant M as Main\\n participant G as Game\\n M->>G: initialize game\\n G->>G: update game state\\n G->>G: draw game\\n G->>G: handle user input\\n G->>G: restart game\\n\",\"Anything UNCLEAR\":\"Clarification needed on the scoring system and how the game over condition will be handled.\"}\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 \"pygame==2.0.1\"\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\": \"Clarification needed on the scoring system and how the game over condition will be handled.\"\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 the Pygame library to create the game and handle user input. The game logic will be implemented using Python classes and functions.\",\"File list\":[\"main.py\",\"game.py\"],\"Data structures and interfaces\":\"\\nclassDiagram\\n class Game {\\n -snake: list\\n -food: tuple\\n -score: int\\n +__init__()\\n +update()\\n +draw()\\n +handle_input()\\n +restart()\\n }\\n class Snake {\\n -body: list\\n -direction: str\\n +__init__(start_pos: tuple, length: int, direction: str)\\n +move()\\n +change_direction(new_direction: str)\\n +grow()\\n }\\n Game --> Snake\\n\",\"Program call flow\":\"\\nsequenceDiagram\\n participant M as Main\\n participant G as Game\\n M->>G: initialize game\\n G->>G: update game state\\n G->>G: draw game\\n G->>G: handle user input\\n G->>G: restart game\\n\",\"Anything UNCLEAR\":\"Clarification needed on the scoring system and how the game over condition will be handled.\"}\n\n## Tasks\n{\"Required Python packages\":[\"pygame==2.0.1\"],\"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\":\"Clarification needed on the scoring system and how the game over condition will be handled.\"}\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 Game:\n def __init__(self):\n self.snake = []\n self.food = (0, 0)\n self.score = 0\n\n def update(self):\n # Update game state\n pass\n\n def draw(self):\n # Draw game\n pass\n\n def handle_input(self):\n # Handle user input\n pass\n\n def restart(self):\n # Restart game\n pass\n\n\nclass Snake:\n def __init__(self, start_pos: tuple, length: int, direction: str):\n self.body = []\n self.direction = direction\n\n def move(self):\n # Move snake\n pass\n\n def change_direction(self, new_direction: str):\n # Change snake direction\n pass\n\n def grow(self):\n # Grow snake\n pass\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 the Pygame library to create the game and handle user input. The game logic will be implemented using Python classes and functions.\",\"File list\":[\"main.py\",\"game.py\"],\"Data structures and interfaces\":\"\\nclassDiagram\\n class Game {\\n -snake: list\\n -food: tuple\\n -score: int\\n +__init__()\\n +update()\\n +draw()\\n +handle_input()\\n +restart()\\n }\\n class Snake {\\n -body: list\\n -direction: str\\n +__init__(start_pos: tuple, length: int, direction: str)\\n +move()\\n +change_direction(new_direction: str)\\n +grow()\\n }\\n Game --> Snake\\n\",\"Program call flow\":\"\\nsequenceDiagram\\n participant M as Main\\n participant G as Game\\n M->>G: initialize game\\n G->>G: update game state\\n G->>G: draw game\\n G->>G: handle user input\\n G->>G: restart game\\n\",\"Anything UNCLEAR\":\"Clarification needed on the scoring system and how the game over condition will be handled.\"}\n\n## Tasks\n{\"Required Python packages\":[\"pygame==2.0.1\"],\"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\":\"Clarification needed on the scoring system and how the game over condition will be handled.\"}\n\n## Code Files\n\n\n\n## Code to be Reviewed: game.py\n```Code\n## game.py\n\nclass Game:\n def __init__(self):\n self.snake = []\n self.food = (0, 0)\n self.score = 0\n\n def update(self):\n # Update game state\n pass\n\n def draw(self):\n # Draw game\n pass\n\n def handle_input(self):\n # Handle user input\n pass\n\n def restart(self):\n # Restart game\n pass\n\n\nclass Snake:\n def __init__(self, start_pos: tuple, length: int, direction: str):\n self.body = []\n self.direction = direction\n\n def move(self):\n # Move snake\n pass\n\n def change_direction(self, new_direction: str):\n # Change snake direction\n pass\n\n def grow(self):\n # Grow snake\n pass\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. No, the code is not implemented as per the requirements. The Game class should have a method to handle game over condition and update the scoring system.\n2. No, the code logic is not completely correct. The Snake class is missing the implementation of the move, change_direction, and grow methods.\n3. Yes, the existing code follows the \"Data structures and interfaces\" provided.\n4. No, the move, change_direction, and grow methods of the Snake class are not implemented. To achieve this, the methods need to be implemented with the appropriate logic for moving the snake, changing its direction, and growing its body.\n5. Yes, all necessary pre-dependencies have been imported.\n6. No, there are no methods from other files being reused in the current code.\n\n## Actions\n1. Implement a method in the Game class to handle the game over condition and update the scoring system.\n2. Implement the move, change_direction, and grow methods in the Snake class.\n3. Reuse methods from other files if necessary.\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 the Pygame library to create the game and handle user input. The game logic will be implemented using Python classes and functions.\",\"File list\":[\"main.py\",\"game.py\"],\"Data structures and interfaces\":\"\\nclassDiagram\\n class Game {\\n -snake: list\\n -food: tuple\\n -score: int\\n +__init__()\\n +update()\\n +draw()\\n +handle_input()\\n +restart()\\n }\\n class Snake {\\n -body: list\\n -direction: str\\n +__init__(start_pos: tuple, length: int, direction: str)\\n +move()\\n +change_direction(new_direction: str)\\n +grow()\\n }\\n Game --> Snake\\n\",\"Program call flow\":\"\\nsequenceDiagram\\n participant M as Main\\n participant G as Game\\n M->>G: initialize game\\n G->>G: update game state\\n G->>G: draw game\\n G->>G: handle user input\\n G->>G: restart game\\n\",\"Anything UNCLEAR\":\"Clarification needed on the scoring system and how the game over condition will be handled.\"}\n\n## Tasks\n{\"Required Python packages\":[\"pygame==2.0.1\"],\"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\":\"Clarification needed on the scoring system and how the game over condition will be handled.\"}\n\n## Code Files\n\n\n\n## Code to be Reviewed: game.py\n```Code\n## game.py\n\nclass Game:\n def __init__(self):\n self.snake = []\n self.food = (0, 0)\n self.score = 0\n\n def update(self):\n # Update game state\n pass\n\n def draw(self):\n # Draw game\n pass\n\n def handle_input(self):\n # Handle user input\n pass\n\n def restart(self):\n # Restart game\n pass\n\n\nclass Snake:\n def __init__(self, start_pos: tuple, length: int, direction: str):\n self.body = []\n self.direction = direction\n\n def move(self):\n # Move snake\n pass\n\n def change_direction(self, new_direction: str):\n # Change snake direction\n pass\n\n def grow(self):\n # Grow snake\n pass\n\n```\n\n## Code Review: game.py\n1. No, the code is not implemented as per the requirements. The Game class should have a method to handle game over condition and update the scoring system.\n2. No, the code logic is not completely correct. The Snake class is missing the implementation of the move, change_direction, and grow methods.\n3. Yes, the existing code follows the \"Data structures and interfaces\" provided.\n4. No, the move, change_direction, and grow methods of the Snake class are not implemented. To achieve this, the methods need to be implemented with the appropriate logic for moving the snake, changing its direction, and growing its body.\n5. Yes, all necessary pre-dependencies have been imported.\n6. No, there are no methods from other files being reused in the current code.\n\n## Actions\n1. Implement a method in the Game class to handle the game over condition and update the scoring system.\n2. Implement the move, change_direction, and grow methods in the Snake class.\n3. Reuse methods from other files if necessary.\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\nclass Game:\n def __init__(self):\n self.snake = []\n self.food = (0, 0)\n self.score = 0\n\n def update(self):\n # Update game state\n pass\n\n def draw(self):\n # Draw game\n pass\n\n def handle_input(self):\n # Handle user input\n pass\n\n def restart(self):\n # Restart game\n pass\n\n def handle_game_over(self):\n # Handle game over condition\n pass\n\n def update_score(self):\n # Update scoring system\n pass\n\n\nclass Snake:\n def __init__(self, start_pos: tuple, length: int, direction: str):\n self.body = []\n self.direction = direction\n\n def move(self):\n # Move snake\n pass\n\n def change_direction(self, new_direction: str):\n # Change snake direction\n pass\n\n def grow(self):\n # Grow snake\n pass\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 the Pygame library to create the game and handle user input. The game logic will be implemented using Python classes and functions.\",\"File list\":[\"main.py\",\"game.py\"],\"Data structures and interfaces\":\"\\nclassDiagram\\n class Game {\\n -snake: list\\n -food: tuple\\n -score: int\\n +__init__()\\n +update()\\n +draw()\\n +handle_input()\\n +restart()\\n }\\n class Snake {\\n -body: list\\n -direction: str\\n +__init__(start_pos: tuple, length: int, direction: str)\\n +move()\\n +change_direction(new_direction: str)\\n +grow()\\n }\\n Game --> Snake\\n\",\"Program call flow\":\"\\nsequenceDiagram\\n participant M as Main\\n participant G as Game\\n M->>G: initialize game\\n G->>G: update game state\\n G->>G: draw game\\n G->>G: handle user input\\n G->>G: restart game\\n\",\"Anything UNCLEAR\":\"Clarification needed on the scoring system and how the game over condition will be handled.\"}\n\n## Tasks\n{\"Required Python packages\":[\"pygame==2.0.1\"],\"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\":\"Clarification needed on the scoring system and how the game over condition will be handled.\"}\n\n## Code Files\n\n\n\n## Code to be Reviewed: game.py\n```Code\n## game.py\n\nclass Game:\n def __init__(self):\n self.snake = []\n self.food = (0, 0)\n self.score = 0\n\n def update(self):\n # Update game state\n pass\n\n def draw(self):\n # Draw game\n pass\n\n def handle_input(self):\n # Handle user input\n pass\n\n def restart(self):\n # Restart game\n pass\n\n def handle_game_over(self):\n # Handle game over condition\n pass\n\n def update_score(self):\n # Update scoring system\n pass\n\n\nclass Snake:\n def __init__(self, start_pos: tuple, length: int, direction: str):\n self.body = []\n self.direction = direction\n\n def move(self):\n # Move snake\n pass\n\n def change_direction(self, new_direction: str):\n # Change snake direction\n pass\n\n def grow(self):\n # Grow snake\n pass\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. No, the code is not implemented as per the requirements. The `handle_game_over` and `update_score` methods are missing, and the `handle_input` method needs to be implemented to handle user input.\n2. The code logic is not completely correct. The `Snake` class `__init__` method does not initialize the `body` attribute with the `start_pos` and `length` parameters.\n3. The existing code follows the \"Data structures and interfaces\" for the `Game` and `Snake` classes.\n4. The `handle_game_over` and `update_score` methods are not implemented. The `handle_input` method needs to be implemented to handle user input.\n5. The necessary pre-dependencies have been imported.\n6. The methods from other files are not being reused in the existing code.\n\n## Actions\n1. Implement the `handle_game_over` method to handle the game over condition.\n2. Implement the `update_score` method to update the scoring system.\n3. Implement the `handle_input` method to handle user input and update the game state accordingly.\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 the Pygame library to create the game and handle user input. The game logic will be implemented using Python classes and functions.\",\"File list\":[\"main.py\",\"game.py\"],\"Data structures and interfaces\":\"\\nclassDiagram\\n class Game {\\n -snake: list\\n -food: tuple\\n -score: int\\n +__init__()\\n +update()\\n +draw()\\n +handle_input()\\n +restart()\\n }\\n class Snake {\\n -body: list\\n -direction: str\\n +__init__(start_pos: tuple, length: int, direction: str)\\n +move()\\n +change_direction(new_direction: str)\\n +grow()\\n }\\n Game --> Snake\\n\",\"Program call flow\":\"\\nsequenceDiagram\\n participant M as Main\\n participant G as Game\\n M->>G: initialize game\\n G->>G: update game state\\n G->>G: draw game\\n G->>G: handle user input\\n G->>G: restart game\\n\",\"Anything UNCLEAR\":\"Clarification needed on the scoring system and how the game over condition will be handled.\"}\n\n## Tasks\n{\"Required Python packages\":[\"pygame==2.0.1\"],\"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\":\"Clarification needed on the scoring system and how the game over condition will be handled.\"}\n\n## Code Files\n\n\n\n## Code to be Reviewed: game.py\n```Code\n## game.py\n\nclass Game:\n def __init__(self):\n self.snake = []\n self.food = (0, 0)\n self.score = 0\n\n def update(self):\n # Update game state\n pass\n\n def draw(self):\n # Draw game\n pass\n\n def handle_input(self):\n # Handle user input\n pass\n\n def restart(self):\n # Restart game\n pass\n\n def handle_game_over(self):\n # Handle game over condition\n pass\n\n def update_score(self):\n # Update scoring system\n pass\n\n\nclass Snake:\n def __init__(self, start_pos: tuple, length: int, direction: str):\n self.body = []\n self.direction = direction\n\n def move(self):\n # Move snake\n pass\n\n def change_direction(self, new_direction: str):\n # Change snake direction\n pass\n\n def grow(self):\n # Grow snake\n pass\n\n```\n\n## Code Review: game.py\n1. No, the code is not implemented as per the requirements. The `handle_game_over` and `update_score` methods are missing, and the `handle_input` method needs to be implemented to handle user input.\n2. The code logic is not completely correct. The `Snake` class `__init__` method does not initialize the `body` attribute with the `start_pos` and `length` parameters.\n3. The existing code follows the \"Data structures and interfaces\" for the `Game` and `Snake` classes.\n4. The `handle_game_over` and `update_score` methods are not implemented. The `handle_input` method needs to be implemented to handle user input.\n5. The necessary pre-dependencies have been imported.\n6. The methods from other files are not being reused in the existing code.\n\n## Actions\n1. Implement the `handle_game_over` method to handle the game over condition.\n2. Implement the `update_score` method to update the scoring system.\n3. Implement the `handle_input` method to handle user input and update the game state accordingly.\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\nclass Game:\n def __init__(self):\n self.snake = []\n self.food = (0, 0)\n self.score = 0\n\n def update(self):\n # Update game state\n pass\n\n def draw(self):\n # Draw game\n pass\n\n def handle_input(self):\n # Handle user input\n pass\n\n def restart(self):\n # Restart game\n pass\n\n def handle_game_over(self):\n # Handle game over condition\n pass\n\n def update_score(self):\n # Update scoring system\n pass\n\n\nclass Snake:\n def __init__(self, start_pos: tuple, length: int, direction: str):\n self.body = [start_pos] # Initialize body with start_pos\n self.direction = direction\n\n def move(self):\n # Move snake\n pass\n\n def change_direction(self, new_direction: str):\n # Change snake direction\n pass\n\n def grow(self):\n # Grow snake\n pass\n```",
- "\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 the Pygame library to create the game and handle user input. The game logic will be implemented using Python classes and functions.\",\"File list\":[\"main.py\",\"game.py\"],\"Data structures and interfaces\":\"\\nclassDiagram\\n class Game {\\n -snake: list\\n -food: tuple\\n -score: int\\n +__init__()\\n +update()\\n +draw()\\n +handle_input()\\n +restart()\\n }\\n class Snake {\\n -body: list\\n -direction: str\\n +__init__(start_pos: tuple, length: int, direction: str)\\n +move()\\n +change_direction(new_direction: str)\\n +grow()\\n }\\n Game --> Snake\\n\",\"Program call flow\":\"\\nsequenceDiagram\\n participant M as Main\\n participant G as Game\\n M->>G: initialize game\\n G->>G: update game state\\n G->>G: draw game\\n G->>G: handle user input\\n G->>G: restart game\\n\",\"Anything UNCLEAR\":\"Clarification needed on the scoring system and how the game over condition will be handled.\"}\n\n## Tasks\n{\"Required Python packages\":[\"pygame==2.0.1\"],\"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\":\"Clarification needed on the scoring system and how the game over condition will be handled.\"}\n\n## Legacy Code\n```Code\n----- game.py\n## game.py\n\nclass Game:\n def __init__(self):\n self.snake = []\n self.food = (0, 0)\n self.score = 0\n\n def update(self):\n # Update game state\n pass\n\n def draw(self):\n # Draw game\n pass\n\n def handle_input(self):\n # Handle user input\n pass\n\n def restart(self):\n # Restart game\n pass\n\n def handle_game_over(self):\n # Handle game over condition\n pass\n\n def update_score(self):\n # Update scoring system\n pass\n\n\nclass Snake:\n def __init__(self, start_pos: tuple, length: int, direction: str):\n self.body = [start_pos] # Initialize body with start_pos\n self.direction = direction\n\n def move(self):\n # Move snake\n pass\n\n def change_direction(self, new_direction: str):\n # Change snake direction\n pass\n\n def grow(self):\n # Grow snake\n pass\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 game = Game()\n running = True\n\n while running:\n game.handle_input()\n game.update()\n game.draw()\n\n if game.handle_game_over():\n game.restart()\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 the Pygame library to create the game and handle user input. The game logic will be implemented using Python classes and functions.\",\"File list\":[\"main.py\",\"game.py\"],\"Data structures and interfaces\":\"\\nclassDiagram\\n class Game {\\n -snake: list\\n -food: tuple\\n -score: int\\n +__init__()\\n +update()\\n +draw()\\n +handle_input()\\n +restart()\\n }\\n class Snake {\\n -body: list\\n -direction: str\\n +__init__(start_pos: tuple, length: int, direction: str)\\n +move()\\n +change_direction(new_direction: str)\\n +grow()\\n }\\n Game --> Snake\\n\",\"Program call flow\":\"\\nsequenceDiagram\\n participant M as Main\\n participant G as Game\\n M->>G: initialize game\\n G->>G: update game state\\n G->>G: draw game\\n G->>G: handle user input\\n G->>G: restart game\\n\",\"Anything UNCLEAR\":\"Clarification needed on the scoring system and how the game over condition will be handled.\"}\n\n## Tasks\n{\"Required Python packages\":[\"pygame==2.0.1\"],\"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\":\"Clarification needed on the scoring system and how the game over condition will be handled.\"}\n\n## Code Files\n----- game.py\n## game.py\n\nclass Game:\n def __init__(self):\n self.snake = []\n self.food = (0, 0)\n self.score = 0\n\n def update(self):\n # Update game state\n pass\n\n def draw(self):\n # Draw game\n pass\n\n def handle_input(self):\n # Handle user input\n pass\n\n def restart(self):\n # Restart game\n pass\n\n def handle_game_over(self):\n # Handle game over condition\n pass\n\n def update_score(self):\n # Update scoring system\n pass\n\n\nclass Snake:\n def __init__(self, start_pos: tuple, length: int, direction: str):\n self.body = [start_pos] # Initialize body with start_pos\n self.direction = direction\n\n def move(self):\n # Move snake\n pass\n\n def change_direction(self, new_direction: str):\n # Change snake direction\n pass\n\n def grow(self):\n # Grow snake\n pass\n\n\n\n## Code to be Reviewed: main.py\n```Code\n## main.py\n\nfrom game import Game\n\ndef main():\n game = Game()\n running = True\n\n while running:\n game.handle_input()\n game.update()\n game.draw()\n\n if game.handle_game_over():\n game.restart()\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, but the game over condition and scoring system need to be clarified and implemented.\n \n## Actions\n1. Clarify and implement the game over condition and scoring system in the `Game` class.\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 the Pygame library to create the game and handle user input. The game logic will be implemented using Python classes and functions.\",\"File list\":[\"main.py\",\"game.py\"],\"Data structures and interfaces\":\"\\nclassDiagram\\n class Game {\\n -snake: list\\n -food: tuple\\n -score: int\\n +__init__()\\n +update()\\n +draw()\\n +handle_input()\\n +restart()\\n }\\n class Snake {\\n -body: list\\n -direction: str\\n +__init__(start_pos: tuple, length: int, direction: str)\\n +move()\\n +change_direction(new_direction: str)\\n +grow()\\n }\\n Game --> Snake\\n\",\"Program call flow\":\"\\nsequenceDiagram\\n participant M as Main\\n participant G as Game\\n M->>G: initialize game\\n G->>G: update game state\\n G->>G: draw game\\n G->>G: handle user input\\n G->>G: restart game\\n\",\"Anything UNCLEAR\":\"Clarification needed on the scoring system and how the game over condition will be handled.\"}\n\n## Tasks\n{\"Required Python packages\":[\"pygame==2.0.1\"],\"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\":\"Clarification needed on the scoring system and how the game over condition will be handled.\"}\n\n## Code Files\n----- game.py\n## game.py\n\nclass Game:\n def __init__(self):\n self.snake = []\n self.food = (0, 0)\n self.score = 0\n\n def update(self):\n # Update game state\n pass\n\n def draw(self):\n # Draw game\n pass\n\n def handle_input(self):\n # Handle user input\n pass\n\n def restart(self):\n # Restart game\n pass\n\n def handle_game_over(self):\n # Handle game over condition\n pass\n\n def update_score(self):\n # Update scoring system\n pass\n\n\nclass Snake:\n def __init__(self, start_pos: tuple, length: int, direction: str):\n self.body = [start_pos] # Initialize body with start_pos\n self.direction = direction\n\n def move(self):\n # Move snake\n pass\n\n def change_direction(self, new_direction: str):\n # Change snake direction\n pass\n\n def grow(self):\n # Grow snake\n pass\n\n\n\n## Code to be Reviewed: main.py\n```Code\n## main.py\n\nfrom game import Game\n\ndef main():\n game = Game()\n running = True\n\n while running:\n game.handle_input()\n game.update()\n game.draw()\n\n if game.handle_game_over():\n game.restart()\n\nif __name__ == \"__main__\":\n main()\n\n```\n\n## Code Review: main.py\n1. The code is implemented as per the requirements, but the game over condition and scoring system need to be clarified and implemented.\n \n## Actions\n1. Clarify and implement the game over condition and scoring system in the `Game` class.\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 main.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## main.py\n...\n```\n": "```python\n## main.py\n\nfrom game import Game\n\ndef main():\n game = Game()\n running = True\n\n while running:\n game.handle_input()\n game.update()\n game.draw()\n\n if game.is_game_over():\n game.restart()\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 the Pygame library to create the game and handle user input. The game logic will be implemented using Python classes and functions.\",\"File list\":[\"main.py\",\"game.py\"],\"Data structures and interfaces\":\"\\nclassDiagram\\n class Game {\\n -snake: list\\n -food: tuple\\n -score: int\\n +__init__()\\n +update()\\n +draw()\\n +handle_input()\\n +restart()\\n }\\n class Snake {\\n -body: list\\n -direction: str\\n +__init__(start_pos: tuple, length: int, direction: str)\\n +move()\\n +change_direction(new_direction: str)\\n +grow()\\n }\\n Game --> Snake\\n\",\"Program call flow\":\"\\nsequenceDiagram\\n participant M as Main\\n participant G as Game\\n M->>G: initialize game\\n G->>G: update game state\\n G->>G: draw game\\n G->>G: handle user input\\n G->>G: restart game\\n\",\"Anything UNCLEAR\":\"Clarification needed on the scoring system and how the game over condition will be handled.\"}\n\n## Tasks\n{\"Required Python packages\":[\"pygame==2.0.1\"],\"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\":\"Clarification needed on the scoring system and how the game over condition will be handled.\"}\n\n## Code Files\n----- game.py\n## game.py\n\nclass Game:\n def __init__(self):\n self.snake = []\n self.food = (0, 0)\n self.score = 0\n\n def update(self):\n # Update game state\n pass\n\n def draw(self):\n # Draw game\n pass\n\n def handle_input(self):\n # Handle user input\n pass\n\n def restart(self):\n # Restart game\n pass\n\n def handle_game_over(self):\n # Handle game over condition\n pass\n\n def update_score(self):\n # Update scoring system\n pass\n\n\nclass Snake:\n def __init__(self, start_pos: tuple, length: int, direction: str):\n self.body = [start_pos] # Initialize body with start_pos\n self.direction = direction\n\n def move(self):\n # Move snake\n pass\n\n def change_direction(self, new_direction: str):\n # Change snake direction\n pass\n\n def grow(self):\n # Grow snake\n pass\n\n\n\n## Code to be Reviewed: main.py\n```Code\n## main.py\n\nfrom game import Game\n\ndef main():\n game = Game()\n running = True\n\n while running:\n game.handle_input()\n game.update()\n game.draw()\n\n if game.is_game_over():\n game.restart()\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, but the `is_game_over` method is missing. To achieve this, we need to implement the `is_game_over` method in the Game class to check for the game over condition.\n2. The code logic is correct, but the missing `is_game_over` method needs to be added to handle the game over condition.\n3. The existing code follows the \"Data structures and interfaces\" as per the provided class diagram.\n4. All functions are implemented, but the `is_game_over` method is missing in the Game class. To achieve this, we need to implement the `is_game_over` method to handle the game over condition.\n5. All necessary pre-dependencies have been imported.\n6. Methods from other files are not being reused in the current code.\n\n## Actions\n1. Implement the `is_game_over` method in the Game class to handle the game over condition.\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 the Pygame library to create the game and handle user input. The game logic will be implemented using Python classes and functions.\",\"File list\":[\"main.py\",\"game.py\"],\"Data structures and interfaces\":\"\\nclassDiagram\\n class Game {\\n -snake: list\\n -food: tuple\\n -score: int\\n +__init__()\\n +update()\\n +draw()\\n +handle_input()\\n +restart()\\n }\\n class Snake {\\n -body: list\\n -direction: str\\n +__init__(start_pos: tuple, length: int, direction: str)\\n +move()\\n +change_direction(new_direction: str)\\n +grow()\\n }\\n Game --> Snake\\n\",\"Program call flow\":\"\\nsequenceDiagram\\n participant M as Main\\n participant G as Game\\n M->>G: initialize game\\n G->>G: update game state\\n G->>G: draw game\\n G->>G: handle user input\\n G->>G: restart game\\n\",\"Anything UNCLEAR\":\"Clarification needed on the scoring system and how the game over condition will be handled.\"}\n\n## Tasks\n{\"Required Python packages\":[\"pygame==2.0.1\"],\"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\":\"Clarification needed on the scoring system and how the game over condition will be handled.\"}\n\n## Code Files\n----- game.py\n## game.py\n\nclass Game:\n def __init__(self):\n self.snake = []\n self.food = (0, 0)\n self.score = 0\n\n def update(self):\n # Update game state\n pass\n\n def draw(self):\n # Draw game\n pass\n\n def handle_input(self):\n # Handle user input\n pass\n\n def restart(self):\n # Restart game\n pass\n\n def handle_game_over(self):\n # Handle game over condition\n pass\n\n def update_score(self):\n # Update scoring system\n pass\n\n\nclass Snake:\n def __init__(self, start_pos: tuple, length: int, direction: str):\n self.body = [start_pos] # Initialize body with start_pos\n self.direction = direction\n\n def move(self):\n # Move snake\n pass\n\n def change_direction(self, new_direction: str):\n # Change snake direction\n pass\n\n def grow(self):\n # Grow snake\n pass\n\n\n\n## Code to be Reviewed: main.py\n```Code\n## main.py\n\nfrom game import Game\n\ndef main():\n game = Game()\n running = True\n\n while running:\n game.handle_input()\n game.update()\n game.draw()\n\n if game.is_game_over():\n game.restart()\n\nif __name__ == \"__main__\":\n main()\n\n```\n\n## Code Review: main.py\n1. The code is implemented as per the requirements, but the `is_game_over` method is missing. To achieve this, we need to implement the `is_game_over` method in the Game class to check for the game over condition.\n2. The code logic is correct, but the missing `is_game_over` method needs to be added to handle the game over condition.\n3. The existing code follows the \"Data structures and interfaces\" as per the provided class diagram.\n4. All functions are implemented, but the `is_game_over` method is missing in the Game class. To achieve this, we need to implement the `is_game_over` method to handle the game over condition.\n5. All necessary pre-dependencies have been imported.\n6. Methods from other files are not being reused in the current code.\n\n## Actions\n1. Implement the `is_game_over` method in the Game class to handle the game over condition.\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 main.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## main.py\n...\n```\n": "```python\n## main.py\n\nfrom game import Game\n\ndef main():\n game = Game()\n running = True\n\n while running:\n game.handle_input()\n game.update()\n game.draw()\n\n if game.is_game_over(): # Check for game over condition\n game.restart()\n\nif __name__ == \"__main__\":\n main()\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@Author : alexanderwu\n@File : write_review.py\n\"\"\"\nimport asyncio\nfrom typing import List, Literal\n\nfrom metagpt.actions import Action\nfrom metagpt.actions.action_node import ActionNode\n\nREVIEW = ActionNode(\n key=\"Review\",\n expected_type=List[str],\n instruction=\"Act as an experienced reviewer and critically assess the given output. Provide specific and\"\n \" constructive feedback, highlighting areas for improvement and suggesting changes.\",\n example=[\n \"The logic in the function `calculate_total` seems flawed. Shouldn't it consider the discount rate as well?\",\n \"The TODO function is not implemented yet? Should we implement it before commit?\",\n ],\n)\n\nREVIEW_RESULT = ActionNode(\n key=\"ReviewResult\",\n expected_type=Literal[\"LGTM\", \"LBTM\"],\n instruction=\"LGTM/LBTM. If the code is fully implemented, \" \"give a LGTM, otherwise provide a LBTM.\",\n example=\"LBTM\",\n)\n\nNEXT_STEPS = ActionNode(\n key=\"NextSteps\",\n expected_type=str,\n instruction=\"Based on the code review outcome, suggest actionable steps. This can include code changes, \"\n \"refactoring suggestions, or any follow-up tasks.\",\n example=\"\"\"1. Refactor the `process_data` method to improve readability and efficiency.\n2. Cover edge cases in the `validate_user` function.\n3. Implement a the TODO in the `calculate_total` function.\n4. 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 ```\n\"\"\",\n)\n\nWRITE_DRAFT = ActionNode(\n key=\"WriteDraft\",\n expected_type=str,\n instruction=\"Could you write draft code for move function in order to implement it?\",\n example=\"Draft: ...\",\n)\n\n\nWRITE_FUNCTION = ActionNode(\n key=\"WriteFunction\",\n expected_type=str,\n instruction=\"write code for the function not implemented.\",\n example=\"\"\"\n```Code\n...\n```\n\"\"\",\n)\n\n\nREWRITE_CODE = ActionNode(\n key=\"RewriteCode\",\n expected_type=str,\n instruction=\"\"\"rewrite code based on the Review and Actions\"\"\",\n example=\"\"\"\n```python\n## example.py\ndef calculate_total(price, quantity):\n total = price * quantity\n```\n\"\"\",\n)\n\n\nCODE_REVIEW_CONTEXT = \"\"\"\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.\n\n# Context\n## System Design\n{\"Implementation approach\": \"我们将使用HTML、CSS和JavaScript来实现这个单机的响应式2048游戏。为了确保游戏性能流畅和响应式设计,我们会选择使用Vue.js框架,因为它易于上手且适合构建交互式界面。我们还将使用localStorage来记录玩家的最高分。\", \"File list\": [\"index.html\", \"styles.css\", \"main.js\", \"game.js\", \"storage.js\"], \"Data structures and interfaces\": \"classDiagram\\\n class Game {\\\n -board Array\\\n -score Number\\\n -bestScore Number\\\n +constructor()\\\n +startGame()\\\n +move(direction: String)\\\n +getBoard() Array\\\n +getScore() Number\\\n +getBestScore() Number\\\n +setBestScore(score: Number)\\\n }\\\n class Storage {\\\n +getBestScore() Number\\\n +setBestScore(score: Number)\\\n }\\\n class Main {\\\n +init()\\\n +bindEvents()\\\n }\\\n Game --> Storage : uses\\\n Main --> Game : uses\", \"Program call flow\": \"sequenceDiagram\\\n participant M as Main\\\n participant G as Game\\\n participant S as Storage\\\n M->>G: init()\\\n G->>S: getBestScore()\\\n S-->>G: return bestScore\\\n M->>G: bindEvents()\\\n M->>G: startGame()\\\n loop Game Loop\\\n M->>G: move(direction)\\\n G->>S: setBestScore(score)\\\n S-->>G: return\\\n end\", \"Anything UNCLEAR\": \"目前项目要求明确,没有不清楚的地方。\"}\n\n## Tasks\n{\"Required Python packages\": [\"无需Python包\"], \"Required Other language third-party packages\": [\"vue.js\"], \"Logic Analysis\": [[\"index.html\", \"作为游戏的入口文件和主要的HTML结构\"], [\"styles.css\", \"包含所有的CSS样式,确保游戏界面美观\"], [\"main.js\", \"包含Main类,负责初始化游戏和绑定事件\"], [\"game.js\", \"包含Game类,负责游戏逻辑,如开始游戏、移动方块等\"], [\"storage.js\", \"包含Storage类,用于获取和设置玩家的最高分\"]], \"Task list\": [\"index.html\", \"styles.css\", \"storage.js\", \"game.js\", \"main.js\"], \"Full API spec\": \"\", \"Shared Knowledge\": \"\\'game.js\\' 包含游戏逻辑相关的函数,被 \\'main.js\\' 调用。\", \"Anything UNCLEAR\": \"目前项目要求明确,没有不清楚的地方。\"}\n\n## Code Files\n----- index.html\n\n\n\n \n \n 2048游戏\n \n \n\n\n
\n
2048
\n
\n
\n
分数
\n
{{ score }}
\n
\n
\n
最高分
\n
{{ bestScore }}
\n
\n
\n
\n
\n
\n {{ cell !== 0 ? cell : \\'\\' }}\n
\n
\n
\n \n
\n\n \n \n \n \n\n\n\n----- styles.css\n/* styles.css */\nbody, html {\n margin: 0;\n padding: 0;\n font-family: \\'Arial\\', sans-serif;\n}\n\n#app {\n text-align: center;\n font-size: 18px;\n color: #776e65;\n}\n\nh1 {\n color: #776e65;\n font-size: 72px;\n font-weight: bold;\n margin: 20px 0;\n}\n\n.scores-container {\n display: flex;\n justify-content: center;\n margin-bottom: 20px;\n}\n\n.score-container, .best-container {\n background: #bbada0;\n padding: 10px;\n border-radius: 5px;\n margin: 0 10px;\n min-width: 100px;\n text-align: center;\n}\n\n.score-header, .best-header {\n color: #eee4da;\n font-size: 18px;\n margin-bottom: 5px;\n}\n\n.game-container {\n max-width: 500px;\n margin: 0 auto 20px;\n background: #bbada0;\n padding: 15px;\n border-radius: 10px;\n position: relative;\n}\n\n.grid-row {\n display: flex;\n}\n\n.grid-cell {\n background: #cdc1b4;\n width: 100px;\n height: 100px;\n margin: 5px;\n display: flex;\n justify-content: center;\n align-items: center;\n font-size: 35px;\n font-weight: bold;\n color: #776e65;\n border-radius: 3px;\n}\n\n/* Dynamic classes for different number cells */\n.number-cell-2 {\n background: #eee4da;\n}\n\n.number-cell-4 {\n background: #ede0c8;\n}\n\n.number-cell-8 {\n background: #f2b179;\n color: #f9f6f2;\n}\n\n.number-cell-16 {\n background: #f59563;\n color: #f9f6f2;\n}\n\n.number-cell-32 {\n background: #f67c5f;\n color: #f9f6f2;\n}\n\n.number-cell-64 {\n background: #f65e3b;\n color: #f9f6f2;\n}\n\n.number-cell-128 {\n background: #edcf72;\n color: #f9f6f2;\n}\n\n.number-cell-256 {\n background: #edcc61;\n color: #f9f6f2;\n}\n\n.number-cell-512 {\n background: #edc850;\n color: #f9f6f2;\n}\n\n.number-cell-1024 {\n background: #edc53f;\n color: #f9f6f2;\n}\n\n.number-cell-2048 {\n background: #edc22e;\n color: #f9f6f2;\n}\n\n/* Larger numbers need smaller font sizes */\n.number-cell-1024, .number-cell-2048 {\n font-size: 30px;\n}\n\nbutton {\n background-color: #8f7a66;\n color: #f9f6f2;\n border: none;\n border-radius: 3px;\n padding: 10px 20px;\n font-size: 18px;\n cursor: pointer;\n outline: none;\n}\n\nbutton:hover {\n background-color: #9f8b76;\n}\n\n----- storage.js\n## storage.js\nclass Storage {\n // 获取最高分\n getBestScore() {\n // 尝试从localStorage中获取最高分,如果不存在则默认为0\n const bestScore = localStorage.getItem(\\'bestScore\\');\n return bestScore ? Number(bestScore) : 0;\n }\n\n // 设置最高分\n setBestScore(score) {\n // 将最高分设置到localStorage中\n localStorage.setItem(\\'bestScore\\', score.toString());\n }\n}\n\n\n\n## Code to be Reviewed: game.js\n```Code\n## game.js\nclass Game {\n constructor() {\n this.board = this.createEmptyBoard();\n this.score = 0;\n this.bestScore = 0;\n }\n\n createEmptyBoard() {\n const board = [];\n for (let i = 0; i < 4; i++) {\n board[i] = [0, 0, 0, 0];\n }\n return board;\n }\n\n startGame() {\n this.board = this.createEmptyBoard();\n this.score = 0;\n this.addRandomTile();\n this.addRandomTile();\n }\n\n addRandomTile() {\n let emptyCells = [];\n for (let r = 0; r < 4; r++) {\n for (let c = 0; c < 4; c++) {\n if (this.board[r][c] === 0) {\n emptyCells.push({ r, c });\n }\n }\n }\n if (emptyCells.length > 0) {\n let randomCell = emptyCells[Math.floor(Math.random() * emptyCells.length)];\n this.board[randomCell.r][randomCell.c] = Math.random() < 0.9 ? 2 : 4;\n }\n }\n\n move(direction) {\n // This function will handle the logic for moving tiles\n // in the specified direction and merging them\n // It will also update the score and add a new random tile if the move is successful\n // The actual implementation of this function is complex and would require\n // a significant amount of code to handle all the cases for moving and merging tiles\n // For the purposes of this example, we will not implement the full logic\n // Instead, we will just call addRandomTile to simulate a move\n this.addRandomTile();\n }\n\n getBoard() {\n return this.board;\n }\n\n getScore() {\n return this.score;\n }\n\n getBestScore() {\n return this.bestScore;\n }\n\n setBestScore(score) {\n this.bestScore = score;\n }\n}\n\n```\n\"\"\"\n\n\nCODE_REVIEW_SMALLEST_CONTEXT = \"\"\"\n## Code to be Reviewed: game.js\n```Code\n// game.js\nclass Game {\n constructor() {\n this.board = this.createEmptyBoard();\n this.score = 0;\n this.bestScore = 0;\n }\n\n createEmptyBoard() {\n const board = [];\n for (let i = 0; i < 4; i++) {\n board[i] = [0, 0, 0, 0];\n }\n return board;\n }\n\n startGame() {\n this.board = this.createEmptyBoard();\n this.score = 0;\n this.addRandomTile();\n this.addRandomTile();\n }\n\n addRandomTile() {\n let emptyCells = [];\n for (let r = 0; r < 4; r++) {\n for (let c = 0; c < 4; c++) {\n if (this.board[r][c] === 0) {\n emptyCells.push({ r, c });\n }\n }\n }\n if (emptyCells.length > 0) {\n let randomCell = emptyCells[Math.floor(Math.random() * emptyCells.length)];\n this.board[randomCell.r][randomCell.c] = Math.random() < 0.9 ? 2 : 4;\n }\n }\n\n move(direction) {\n // This function will handle the logic for moving tiles\n // in the specified direction and merging them\n // It will also update the score and add a new random tile if the move is successful\n // The actual implementation of this function is complex and would require\n // a significant amount of code to handle all the cases for moving and merging tiles\n // For the purposes of this example, we will not implement the full logic\n // Instead, we will just call addRandomTile to simulate a move\n this.addRandomTile();\n }\n\n getBoard() {\n return this.board;\n }\n\n getScore() {\n return this.score;\n }\n\n getBestScore() {\n return this.bestScore;\n }\n\n setBestScore(score) {\n this.bestScore = score;\n }\n}\n\n```\n\"\"\"\n\n\nCODE_REVIEW_SAMPLE = \"\"\"\n## Code Review: game.js\n1. The code partially implements the requirements. The `Game` class is missing the full implementation of the `move` method, which is crucial for the game\\'s functionality.\n2. The code logic is not completely correct. The `move` method is not implemented, which means the game cannot process player moves.\n3. The existing code follows the \"Data structures and interfaces\" in terms of class structure but lacks full method implementations.\n4. Not all functions are implemented. The `move` method is incomplete and does not handle the logic for moving and merging tiles.\n5. All necessary pre-dependencies seem to be imported since the code does not indicate the need for additional imports.\n6. The methods from other files (such as `Storage`) are not being used in the provided code snippet, but the class structure suggests that they will be used correctly.\n\n## Actions\n1. Implement the `move` method to handle tile movements and merging. This is a complex task that requires careful consideration of the game\\'s rules and logic. Here is a simplified version of how one might begin to implement the `move` method:\n ```javascript\n move(direction) {\n // Simplified logic for moving tiles up\n if (direction === \\'up\\') {\n for (let col = 0; col < 4; col++) {\n let tiles = this.board.map(row => row[col]).filter(val => val !== 0);\n let merged = [];\n for (let i = 0; i < tiles.length; i++) {\n if (tiles[i] === tiles[i + 1]) {\n tiles[i] *= 2;\n this.score += tiles[i];\n tiles[i + 1] = 0;\n merged.push(i);\n }\n }\n tiles = tiles.filter(val => val !== 0);\n while (tiles.length < 4) {\n tiles.push(0);\n }\n for (let row = 0; row < 4; row++) {\n this.board[row][col] = tiles[row];\n }\n }\n }\n // Additional logic needed for \\'down\\', \\'left\\', \\'right\\'\n // ...\n this.addRandomTile();\n }\n ```\n2. Integrate the `Storage` class methods to handle the best score. This means updating the `startGame` and `setBestScore` methods to use `Storage` for retrieving and setting the best score:\n ```javascript\n startGame() {\n this.board = this.createEmptyBoard();\n this.score = 0;\n this.bestScore = new Storage().getBestScore(); // Retrieve the best score from storage\n this.addRandomTile();\n this.addRandomTile();\n }\n\n setBestScore(score) {\n if (score > this.bestScore) {\n this.bestScore = score;\n new Storage().setBestScore(score); // Set the new best score in storage\n }\n }\n ```\n\n## Code Review Result\nLBTM\n\n```\n\"\"\"\n\n\nWRITE_CODE_NODE = ActionNode.from_children(\"WRITE_REVIEW_NODE\", [REVIEW, REVIEW_RESULT, NEXT_STEPS])\nWRITE_MOVE_NODE = ActionNode.from_children(\"WRITE_MOVE_NODE\", [WRITE_DRAFT, WRITE_FUNCTION])\n\n\nCR_FOR_MOVE_FUNCTION_BY_3 = \"\"\"\nThe move function implementation provided appears to be well-structured and follows a clear logic for moving and merging tiles in the specified direction. However, there are a few potential improvements that could be made to enhance the code:\n\n1. Encapsulation: The logic for moving and merging tiles could be encapsulated into smaller, reusable functions to improve readability and maintainability.\n\n2. Magic Numbers: There are some magic numbers (e.g., 4, 3) used in the loops that could be replaced with named constants for improved readability and easier maintenance.\n\n3. Comments: Adding comments to explain the logic and purpose of each section of the code can improve understanding for future developers who may need to work on or maintain the code.\n\n4. Error Handling: It's important to consider error handling for unexpected input or edge cases to ensure the function behaves as expected in all scenarios.\n\nOverall, the code could benefit from refactoring to improve readability, maintainability, and extensibility. If you would like, I can provide a refactored version of the move function that addresses these considerations.\n\"\"\"\n\n\nclass WriteCodeAN(Action):\n \"\"\"Write a code review for the context.\"\"\"\n\n async def run(self, context):\n self.llm.system_prompt = \"You are an outstanding engineer and can implement any code\"\n return await WRITE_MOVE_NODE.fill(context=context, llm=self.llm, schema=\"json\")\n\n\nasync def main():\n await WriteCodeAN().run(CODE_REVIEW_SMALLEST_CONTEXT)\n\n\nif __name__ == \"__main__\":\n asyncio.run(main())\n\n```\n\n---\nTranslate the code above into Mermaid Sequence Diagram.": "```mermaid\nsequenceDiagram\n participant User\n participant WriteCodeAN\n participant WRITE_MOVE_NODE\n\n User->>WriteCodeAN: Run\n WriteCodeAN->>WRITE_MOVE_NODE: Fill\n WRITE_MOVE_NODE->>WriteCodeAN: Filled\n WriteCodeAN->>User: Return Filled Context\n```",
+ "\n## context\n{\"Implementation approach\":\"We will use the Pygame library to create the game and handle user input. The game logic will be implemented using Python classes and functions.\",\"File list\":[\"main.py\",\"game.py\"],\"Data structures and interfaces\":\"\\nclassDiagram\\n class Game {\\n -snake: list\\n -food: tuple\\n -score: int\\n +__init__()\\n +update()\\n +draw()\\n +handle_input()\\n +restart()\\n }\\n class Snake {\\n -body: list\\n -direction: str\\n +__init__(start_pos: tuple, length: int, direction: str)\\n +move()\\n +change_direction(new_direction: str)\\n +grow()\\n }\\n Game --> Snake\\n\",\"Program call flow\":\"\\nsequenceDiagram\\n participant M as Main\\n participant G as Game\\n M->>G: initialize game\\n G->>G: update game state\\n G->>G: draw game\\n G->>G: handle user input\\n G->>G: restart game\\n\",\"Anything UNCLEAR\":\"Clarification needed on the scoring system and how the game over condition will be handled.\"}\n\n-----\n\n## format example\n[CONTENT]\n{\n \"Required 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 packages: typing.List[str] # Provide Required 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 packages\": [\n \"pygame==2.0.1\"\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\": \"Clarification needed on the scoring system and how the game over condition will be handled.\"\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 the Pygame library to create the game and handle user input. The game logic will be implemented using Python classes and functions.\",\"File list\":[\"main.py\",\"game.py\"],\"Data structures and interfaces\":\"\\nclassDiagram\\n class Game {\\n -snake: list\\n -food: tuple\\n -score: int\\n +__init__()\\n +update()\\n +draw()\\n +handle_input()\\n +restart()\\n }\\n class Snake {\\n -body: list\\n -direction: str\\n +__init__(start_pos: tuple, length: int, direction: str)\\n +move()\\n +change_direction(new_direction: str)\\n +grow()\\n }\\n Game --> Snake\\n\",\"Program call flow\":\"\\nsequenceDiagram\\n participant M as Main\\n participant G as Game\\n M->>G: initialize game\\n G->>G: update game state\\n G->>G: draw game\\n G->>G: handle user input\\n G->>G: restart game\\n\",\"Anything UNCLEAR\":\"Clarification needed on the scoring system and how the game over condition will be handled.\"}\n\n## Tasks\n{\"Required packages\":[\"pygame==2.0.1\"],\"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\":\"Clarification needed on the scoring system and how the game over condition will be handled.\"}\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 Game:\n def __init__(self):\n self.snake = []\n self.food = (0, 0)\n self.score = 0\n\n def update(self):\n # Update game state\n pass\n\n def draw(self):\n # Draw game\n pass\n\n def handle_input(self):\n # Handle user input\n pass\n\n def restart(self):\n # Restart game\n pass\n\n\nclass Snake:\n def __init__(self, start_pos: tuple, length: int, direction: str):\n self.body = []\n self.direction = direction\n\n def move(self):\n # Move snake\n pass\n\n def change_direction(self, new_direction: str):\n # Change snake direction\n pass\n\n def grow(self):\n # Grow snake\n pass\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 the Pygame library to create the game and handle user input. The game logic will be implemented using Python classes and functions.\",\"File list\":[\"main.py\",\"game.py\"],\"Data structures and interfaces\":\"\\nclassDiagram\\n class Game {\\n -snake: list\\n -food: tuple\\n -score: int\\n +__init__()\\n +update()\\n +draw()\\n +handle_input()\\n +restart()\\n }\\n class Snake {\\n -body: list\\n -direction: str\\n +__init__(start_pos: tuple, length: int, direction: str)\\n +move()\\n +change_direction(new_direction: str)\\n +grow()\\n }\\n Game --> Snake\\n\",\"Program call flow\":\"\\nsequenceDiagram\\n participant M as Main\\n participant G as Game\\n M->>G: initialize game\\n G->>G: update game state\\n G->>G: draw game\\n G->>G: handle user input\\n G->>G: restart game\\n\",\"Anything UNCLEAR\":\"Clarification needed on the scoring system and how the game over condition will be handled.\"}\n\n## Tasks\n{\"Required packages\":[\"pygame==2.0.1\"],\"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\":\"Clarification needed on the scoring system and how the game over condition will be handled.\"}\n\n## Code Files\n\n\n\n## Code to be Reviewed: game.py\n```Code\n## game.py\n\nclass Game:\n def __init__(self):\n self.snake = []\n self.food = (0, 0)\n self.score = 0\n\n def update(self):\n # Update game state\n pass\n\n def draw(self):\n # Draw game\n pass\n\n def handle_input(self):\n # Handle user input\n pass\n\n def restart(self):\n # Restart game\n pass\n\n\nclass Snake:\n def __init__(self, start_pos: tuple, length: int, direction: str):\n self.body = []\n self.direction = direction\n\n def move(self):\n # Move snake\n pass\n\n def change_direction(self, new_direction: str):\n # Change snake direction\n pass\n\n def grow(self):\n # Grow snake\n pass\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. No, the code is not implemented as per the requirements. The Game class should have a method to handle game over condition and update the scoring system.\n2. No, the code logic is not completely correct. The Snake class is missing the implementation of the move, change_direction, and grow methods.\n3. Yes, the existing code follows the \"Data structures and interfaces\" provided.\n4. No, the move, change_direction, and grow methods of the Snake class are not implemented. To achieve this, the methods need to be implemented with the appropriate logic for moving the snake, changing its direction, and growing its body.\n5. Yes, all necessary pre-dependencies have been imported.\n6. No, there are no methods from other files being reused in the current code.\n\n## Actions\n1. Implement a method in the Game class to handle the game over condition and update the scoring system.\n2. Implement the move, change_direction, and grow methods in the Snake class.\n3. Reuse methods from other files if necessary.\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 the Pygame library to create the game and handle user input. The game logic will be implemented using Python classes and functions.\",\"File list\":[\"main.py\",\"game.py\"],\"Data structures and interfaces\":\"\\nclassDiagram\\n class Game {\\n -snake: list\\n -food: tuple\\n -score: int\\n +__init__()\\n +update()\\n +draw()\\n +handle_input()\\n +restart()\\n }\\n class Snake {\\n -body: list\\n -direction: str\\n +__init__(start_pos: tuple, length: int, direction: str)\\n +move()\\n +change_direction(new_direction: str)\\n +grow()\\n }\\n Game --> Snake\\n\",\"Program call flow\":\"\\nsequenceDiagram\\n participant M as Main\\n participant G as Game\\n M->>G: initialize game\\n G->>G: update game state\\n G->>G: draw game\\n G->>G: handle user input\\n G->>G: restart game\\n\",\"Anything UNCLEAR\":\"Clarification needed on the scoring system and how the game over condition will be handled.\"}\n\n## Tasks\n{\"Required packages\":[\"pygame==2.0.1\"],\"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\":\"Clarification needed on the scoring system and how the game over condition will be handled.\"}\n\n## Code Files\n\n\n\n## Code to be Reviewed: game.py\n```Code\n## game.py\n\nclass Game:\n def __init__(self):\n self.snake = []\n self.food = (0, 0)\n self.score = 0\n\n def update(self):\n # Update game state\n pass\n\n def draw(self):\n # Draw game\n pass\n\n def handle_input(self):\n # Handle user input\n pass\n\n def restart(self):\n # Restart game\n pass\n\n\nclass Snake:\n def __init__(self, start_pos: tuple, length: int, direction: str):\n self.body = []\n self.direction = direction\n\n def move(self):\n # Move snake\n pass\n\n def change_direction(self, new_direction: str):\n # Change snake direction\n pass\n\n def grow(self):\n # Grow snake\n pass\n\n```\n\n## Code Review: game.py\n1. No, the code is not implemented as per the requirements. The Game class should have a method to handle game over condition and update the scoring system.\n2. No, the code logic is not completely correct. The Snake class is missing the implementation of the move, change_direction, and grow methods.\n3. Yes, the existing code follows the \"Data structures and interfaces\" provided.\n4. No, the move, change_direction, and grow methods of the Snake class are not implemented. To achieve this, the methods need to be implemented with the appropriate logic for moving the snake, changing its direction, and growing its body.\n5. Yes, all necessary pre-dependencies have been imported.\n6. No, there are no methods from other files being reused in the current code.\n\n## Actions\n1. Implement a method in the Game class to handle the game over condition and update the scoring system.\n2. Implement the move, change_direction, and grow methods in the Snake class.\n3. Reuse methods from other files if necessary.\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\nclass Game:\n def __init__(self):\n self.snake = []\n self.food = (0, 0)\n self.score = 0\n\n def update(self):\n # Update game state\n pass\n\n def draw(self):\n # Draw game\n pass\n\n def handle_input(self):\n # Handle user input\n pass\n\n def restart(self):\n # Restart game\n pass\n\n def handle_game_over(self):\n # Handle game over condition\n pass\n\n def update_score(self):\n # Update scoring system\n pass\n\n\nclass Snake:\n def __init__(self, start_pos: tuple, length: int, direction: str):\n self.body = []\n self.direction = direction\n\n def move(self):\n # Move snake\n pass\n\n def change_direction(self, new_direction: str):\n # Change snake direction\n pass\n\n def grow(self):\n # Grow snake\n pass\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 the Pygame library to create the game and handle user input. The game logic will be implemented using Python classes and functions.\",\"File list\":[\"main.py\",\"game.py\"],\"Data structures and interfaces\":\"\\nclassDiagram\\n class Game {\\n -snake: list\\n -food: tuple\\n -score: int\\n +__init__()\\n +update()\\n +draw()\\n +handle_input()\\n +restart()\\n }\\n class Snake {\\n -body: list\\n -direction: str\\n +__init__(start_pos: tuple, length: int, direction: str)\\n +move()\\n +change_direction(new_direction: str)\\n +grow()\\n }\\n Game --> Snake\\n\",\"Program call flow\":\"\\nsequenceDiagram\\n participant M as Main\\n participant G as Game\\n M->>G: initialize game\\n G->>G: update game state\\n G->>G: draw game\\n G->>G: handle user input\\n G->>G: restart game\\n\",\"Anything UNCLEAR\":\"Clarification needed on the scoring system and how the game over condition will be handled.\"}\n\n## Tasks\n{\"Required packages\":[\"pygame==2.0.1\"],\"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\":\"Clarification needed on the scoring system and how the game over condition will be handled.\"}\n\n## Code Files\n\n\n\n## Code to be Reviewed: game.py\n```Code\n## game.py\n\nclass Game:\n def __init__(self):\n self.snake = []\n self.food = (0, 0)\n self.score = 0\n\n def update(self):\n # Update game state\n pass\n\n def draw(self):\n # Draw game\n pass\n\n def handle_input(self):\n # Handle user input\n pass\n\n def restart(self):\n # Restart game\n pass\n\n def handle_game_over(self):\n # Handle game over condition\n pass\n\n def update_score(self):\n # Update scoring system\n pass\n\n\nclass Snake:\n def __init__(self, start_pos: tuple, length: int, direction: str):\n self.body = []\n self.direction = direction\n\n def move(self):\n # Move snake\n pass\n\n def change_direction(self, new_direction: str):\n # Change snake direction\n pass\n\n def grow(self):\n # Grow snake\n pass\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. No, the code is not implemented as per the requirements. The `handle_game_over` and `update_score` methods are missing, and the `handle_input` method needs to be implemented to handle user input.\n2. The code logic is not completely correct. The `Snake` class `__init__` method does not initialize the `body` attribute with the `start_pos` and `length` parameters.\n3. The existing code follows the \"Data structures and interfaces\" for the `Game` and `Snake` classes.\n4. The `handle_game_over` and `update_score` methods are not implemented. The `handle_input` method needs to be implemented to handle user input.\n5. The necessary pre-dependencies have been imported.\n6. The methods from other files are not being reused in the existing code.\n\n## Actions\n1. Implement the `handle_game_over` method to handle the game over condition.\n2. Implement the `update_score` method to update the scoring system.\n3. Implement the `handle_input` method to handle user input and update the game state accordingly.\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 the Pygame library to create the game and handle user input. The game logic will be implemented using Python classes and functions.\",\"File list\":[\"main.py\",\"game.py\"],\"Data structures and interfaces\":\"\\nclassDiagram\\n class Game {\\n -snake: list\\n -food: tuple\\n -score: int\\n +__init__()\\n +update()\\n +draw()\\n +handle_input()\\n +restart()\\n }\\n class Snake {\\n -body: list\\n -direction: str\\n +__init__(start_pos: tuple, length: int, direction: str)\\n +move()\\n +change_direction(new_direction: str)\\n +grow()\\n }\\n Game --> Snake\\n\",\"Program call flow\":\"\\nsequenceDiagram\\n participant M as Main\\n participant G as Game\\n M->>G: initialize game\\n G->>G: update game state\\n G->>G: draw game\\n G->>G: handle user input\\n G->>G: restart game\\n\",\"Anything UNCLEAR\":\"Clarification needed on the scoring system and how the game over condition will be handled.\"}\n\n## Tasks\n{\"Required packages\":[\"pygame==2.0.1\"],\"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\":\"Clarification needed on the scoring system and how the game over condition will be handled.\"}\n\n## Code Files\n\n\n\n## Code to be Reviewed: game.py\n```Code\n## game.py\n\nclass Game:\n def __init__(self):\n self.snake = []\n self.food = (0, 0)\n self.score = 0\n\n def update(self):\n # Update game state\n pass\n\n def draw(self):\n # Draw game\n pass\n\n def handle_input(self):\n # Handle user input\n pass\n\n def restart(self):\n # Restart game\n pass\n\n def handle_game_over(self):\n # Handle game over condition\n pass\n\n def update_score(self):\n # Update scoring system\n pass\n\n\nclass Snake:\n def __init__(self, start_pos: tuple, length: int, direction: str):\n self.body = []\n self.direction = direction\n\n def move(self):\n # Move snake\n pass\n\n def change_direction(self, new_direction: str):\n # Change snake direction\n pass\n\n def grow(self):\n # Grow snake\n pass\n\n```\n\n## Code Review: game.py\n1. No, the code is not implemented as per the requirements. The `handle_game_over` and `update_score` methods are missing, and the `handle_input` method needs to be implemented to handle user input.\n2. The code logic is not completely correct. The `Snake` class `__init__` method does not initialize the `body` attribute with the `start_pos` and `length` parameters.\n3. The existing code follows the \"Data structures and interfaces\" for the `Game` and `Snake` classes.\n4. The `handle_game_over` and `update_score` methods are not implemented. The `handle_input` method needs to be implemented to handle user input.\n5. The necessary pre-dependencies have been imported.\n6. The methods from other files are not being reused in the existing code.\n\n## Actions\n1. Implement the `handle_game_over` method to handle the game over condition.\n2. Implement the `update_score` method to update the scoring system.\n3. Implement the `handle_input` method to handle user input and update the game state accordingly.\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\nclass Game:\n def __init__(self):\n self.snake = []\n self.food = (0, 0)\n self.score = 0\n\n def update(self):\n # Update game state\n pass\n\n def draw(self):\n # Draw game\n pass\n\n def handle_input(self):\n # Handle user input\n pass\n\n def restart(self):\n # Restart game\n pass\n\n def handle_game_over(self):\n # Handle game over condition\n pass\n\n def update_score(self):\n # Update scoring system\n pass\n\n\nclass Snake:\n def __init__(self, start_pos: tuple, length: int, direction: str):\n self.body = [start_pos] # Initialize body with start_pos\n self.direction = direction\n\n def move(self):\n # Move snake\n pass\n\n def change_direction(self, new_direction: str):\n # Change snake direction\n pass\n\n def grow(self):\n # Grow snake\n pass\n```",
+ "\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 the Pygame library to create the game and handle user input. The game logic will be implemented using Python classes and functions.\",\"File list\":[\"main.py\",\"game.py\"],\"Data structures and interfaces\":\"\\nclassDiagram\\n class Game {\\n -snake: list\\n -food: tuple\\n -score: int\\n +__init__()\\n +update()\\n +draw()\\n +handle_input()\\n +restart()\\n }\\n class Snake {\\n -body: list\\n -direction: str\\n +__init__(start_pos: tuple, length: int, direction: str)\\n +move()\\n +change_direction(new_direction: str)\\n +grow()\\n }\\n Game --> Snake\\n\",\"Program call flow\":\"\\nsequenceDiagram\\n participant M as Main\\n participant G as Game\\n M->>G: initialize game\\n G->>G: update game state\\n G->>G: draw game\\n G->>G: handle user input\\n G->>G: restart game\\n\",\"Anything UNCLEAR\":\"Clarification needed on the scoring system and how the game over condition will be handled.\"}\n\n## Tasks\n{\"Required packages\":[\"pygame==2.0.1\"],\"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\":\"Clarification needed on the scoring system and how the game over condition will be handled.\"}\n\n## Legacy Code\n```Code\n----- game.py\n## game.py\n\nclass Game:\n def __init__(self):\n self.snake = []\n self.food = (0, 0)\n self.score = 0\n\n def update(self):\n # Update game state\n pass\n\n def draw(self):\n # Draw game\n pass\n\n def handle_input(self):\n # Handle user input\n pass\n\n def restart(self):\n # Restart game\n pass\n\n def handle_game_over(self):\n # Handle game over condition\n pass\n\n def update_score(self):\n # Update scoring system\n pass\n\n\nclass Snake:\n def __init__(self, start_pos: tuple, length: int, direction: str):\n self.body = [start_pos] # Initialize body with start_pos\n self.direction = direction\n\n def move(self):\n # Move snake\n pass\n\n def change_direction(self, new_direction: str):\n # Change snake direction\n pass\n\n def grow(self):\n # Grow snake\n pass\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 game = Game()\n running = True\n\n while running:\n game.handle_input()\n game.update()\n game.draw()\n\n if game.handle_game_over():\n game.restart()\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 the Pygame library to create the game and handle user input. The game logic will be implemented using Python classes and functions.\",\"File list\":[\"main.py\",\"game.py\"],\"Data structures and interfaces\":\"\\nclassDiagram\\n class Game {\\n -snake: list\\n -food: tuple\\n -score: int\\n +__init__()\\n +update()\\n +draw()\\n +handle_input()\\n +restart()\\n }\\n class Snake {\\n -body: list\\n -direction: str\\n +__init__(start_pos: tuple, length: int, direction: str)\\n +move()\\n +change_direction(new_direction: str)\\n +grow()\\n }\\n Game --> Snake\\n\",\"Program call flow\":\"\\nsequenceDiagram\\n participant M as Main\\n participant G as Game\\n M->>G: initialize game\\n G->>G: update game state\\n G->>G: draw game\\n G->>G: handle user input\\n G->>G: restart game\\n\",\"Anything UNCLEAR\":\"Clarification needed on the scoring system and how the game over condition will be handled.\"}\n\n## Tasks\n{\"Required packages\":[\"pygame==2.0.1\"],\"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\":\"Clarification needed on the scoring system and how the game over condition will be handled.\"}\n\n## Code Files\n----- game.py\n## game.py\n\nclass Game:\n def __init__(self):\n self.snake = []\n self.food = (0, 0)\n self.score = 0\n\n def update(self):\n # Update game state\n pass\n\n def draw(self):\n # Draw game\n pass\n\n def handle_input(self):\n # Handle user input\n pass\n\n def restart(self):\n # Restart game\n pass\n\n def handle_game_over(self):\n # Handle game over condition\n pass\n\n def update_score(self):\n # Update scoring system\n pass\n\n\nclass Snake:\n def __init__(self, start_pos: tuple, length: int, direction: str):\n self.body = [start_pos] # Initialize body with start_pos\n self.direction = direction\n\n def move(self):\n # Move snake\n pass\n\n def change_direction(self, new_direction: str):\n # Change snake direction\n pass\n\n def grow(self):\n # Grow snake\n pass\n\n\n\n## Code to be Reviewed: main.py\n```Code\n## main.py\n\nfrom game import Game\n\ndef main():\n game = Game()\n running = True\n\n while running:\n game.handle_input()\n game.update()\n game.draw()\n\n if game.handle_game_over():\n game.restart()\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, but the game over condition and scoring system need to be clarified and implemented.\n \n## Actions\n1. Clarify and implement the game over condition and scoring system in the `Game` class.\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 the Pygame library to create the game and handle user input. The game logic will be implemented using Python classes and functions.\",\"File list\":[\"main.py\",\"game.py\"],\"Data structures and interfaces\":\"\\nclassDiagram\\n class Game {\\n -snake: list\\n -food: tuple\\n -score: int\\n +__init__()\\n +update()\\n +draw()\\n +handle_input()\\n +restart()\\n }\\n class Snake {\\n -body: list\\n -direction: str\\n +__init__(start_pos: tuple, length: int, direction: str)\\n +move()\\n +change_direction(new_direction: str)\\n +grow()\\n }\\n Game --> Snake\\n\",\"Program call flow\":\"\\nsequenceDiagram\\n participant M as Main\\n participant G as Game\\n M->>G: initialize game\\n G->>G: update game state\\n G->>G: draw game\\n G->>G: handle user input\\n G->>G: restart game\\n\",\"Anything UNCLEAR\":\"Clarification needed on the scoring system and how the game over condition will be handled.\"}\n\n## Tasks\n{\"Required packages\":[\"pygame==2.0.1\"],\"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\":\"Clarification needed on the scoring system and how the game over condition will be handled.\"}\n\n## Code Files\n----- game.py\n## game.py\n\nclass Game:\n def __init__(self):\n self.snake = []\n self.food = (0, 0)\n self.score = 0\n\n def update(self):\n # Update game state\n pass\n\n def draw(self):\n # Draw game\n pass\n\n def handle_input(self):\n # Handle user input\n pass\n\n def restart(self):\n # Restart game\n pass\n\n def handle_game_over(self):\n # Handle game over condition\n pass\n\n def update_score(self):\n # Update scoring system\n pass\n\n\nclass Snake:\n def __init__(self, start_pos: tuple, length: int, direction: str):\n self.body = [start_pos] # Initialize body with start_pos\n self.direction = direction\n\n def move(self):\n # Move snake\n pass\n\n def change_direction(self, new_direction: str):\n # Change snake direction\n pass\n\n def grow(self):\n # Grow snake\n pass\n\n\n\n## Code to be Reviewed: main.py\n```Code\n## main.py\n\nfrom game import Game\n\ndef main():\n game = Game()\n running = True\n\n while running:\n game.handle_input()\n game.update()\n game.draw()\n\n if game.handle_game_over():\n game.restart()\n\nif __name__ == \"__main__\":\n main()\n\n```\n\n## Code Review: main.py\n1. The code is implemented as per the requirements, but the game over condition and scoring system need to be clarified and implemented.\n \n## Actions\n1. Clarify and implement the game over condition and scoring system in the `Game` class.\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 main.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## main.py\n...\n```\n": "```python\n## main.py\n\nfrom game import Game\n\ndef main():\n game = Game()\n running = True\n\n while running:\n game.handle_input()\n game.update()\n game.draw()\n\n if game.is_game_over():\n game.restart()\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 the Pygame library to create the game and handle user input. The game logic will be implemented using Python classes and functions.\",\"File list\":[\"main.py\",\"game.py\"],\"Data structures and interfaces\":\"\\nclassDiagram\\n class Game {\\n -snake: list\\n -food: tuple\\n -score: int\\n +__init__()\\n +update()\\n +draw()\\n +handle_input()\\n +restart()\\n }\\n class Snake {\\n -body: list\\n -direction: str\\n +__init__(start_pos: tuple, length: int, direction: str)\\n +move()\\n +change_direction(new_direction: str)\\n +grow()\\n }\\n Game --> Snake\\n\",\"Program call flow\":\"\\nsequenceDiagram\\n participant M as Main\\n participant G as Game\\n M->>G: initialize game\\n G->>G: update game state\\n G->>G: draw game\\n G->>G: handle user input\\n G->>G: restart game\\n\",\"Anything UNCLEAR\":\"Clarification needed on the scoring system and how the game over condition will be handled.\"}\n\n## Tasks\n{\"Required packages\":[\"pygame==2.0.1\"],\"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\":\"Clarification needed on the scoring system and how the game over condition will be handled.\"}\n\n## Code Files\n----- game.py\n## game.py\n\nclass Game:\n def __init__(self):\n self.snake = []\n self.food = (0, 0)\n self.score = 0\n\n def update(self):\n # Update game state\n pass\n\n def draw(self):\n # Draw game\n pass\n\n def handle_input(self):\n # Handle user input\n pass\n\n def restart(self):\n # Restart game\n pass\n\n def handle_game_over(self):\n # Handle game over condition\n pass\n\n def update_score(self):\n # Update scoring system\n pass\n\n\nclass Snake:\n def __init__(self, start_pos: tuple, length: int, direction: str):\n self.body = [start_pos] # Initialize body with start_pos\n self.direction = direction\n\n def move(self):\n # Move snake\n pass\n\n def change_direction(self, new_direction: str):\n # Change snake direction\n pass\n\n def grow(self):\n # Grow snake\n pass\n\n\n\n## Code to be Reviewed: main.py\n```Code\n## main.py\n\nfrom game import Game\n\ndef main():\n game = Game()\n running = True\n\n while running:\n game.handle_input()\n game.update()\n game.draw()\n\n if game.is_game_over():\n game.restart()\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, but the `is_game_over` method is missing. To achieve this, we need to implement the `is_game_over` method in the Game class to check for the game over condition.\n2. The code logic is correct, but the missing `is_game_over` method needs to be added to handle the game over condition.\n3. The existing code follows the \"Data structures and interfaces\" as per the provided class diagram.\n4. All functions are implemented, but the `is_game_over` method is missing in the Game class. To achieve this, we need to implement the `is_game_over` method to handle the game over condition.\n5. All necessary pre-dependencies have been imported.\n6. Methods from other files are not being reused in the current code.\n\n## Actions\n1. Implement the `is_game_over` method in the Game class to handle the game over condition.\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 the Pygame library to create the game and handle user input. The game logic will be implemented using Python classes and functions.\",\"File list\":[\"main.py\",\"game.py\"],\"Data structures and interfaces\":\"\\nclassDiagram\\n class Game {\\n -snake: list\\n -food: tuple\\n -score: int\\n +__init__()\\n +update()\\n +draw()\\n +handle_input()\\n +restart()\\n }\\n class Snake {\\n -body: list\\n -direction: str\\n +__init__(start_pos: tuple, length: int, direction: str)\\n +move()\\n +change_direction(new_direction: str)\\n +grow()\\n }\\n Game --> Snake\\n\",\"Program call flow\":\"\\nsequenceDiagram\\n participant M as Main\\n participant G as Game\\n M->>G: initialize game\\n G->>G: update game state\\n G->>G: draw game\\n G->>G: handle user input\\n G->>G: restart game\\n\",\"Anything UNCLEAR\":\"Clarification needed on the scoring system and how the game over condition will be handled.\"}\n\n## Tasks\n{\"Required packages\":[\"pygame==2.0.1\"],\"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\":\"Clarification needed on the scoring system and how the game over condition will be handled.\"}\n\n## Code Files\n----- game.py\n## game.py\n\nclass Game:\n def __init__(self):\n self.snake = []\n self.food = (0, 0)\n self.score = 0\n\n def update(self):\n # Update game state\n pass\n\n def draw(self):\n # Draw game\n pass\n\n def handle_input(self):\n # Handle user input\n pass\n\n def restart(self):\n # Restart game\n pass\n\n def handle_game_over(self):\n # Handle game over condition\n pass\n\n def update_score(self):\n # Update scoring system\n pass\n\n\nclass Snake:\n def __init__(self, start_pos: tuple, length: int, direction: str):\n self.body = [start_pos] # Initialize body with start_pos\n self.direction = direction\n\n def move(self):\n # Move snake\n pass\n\n def change_direction(self, new_direction: str):\n # Change snake direction\n pass\n\n def grow(self):\n # Grow snake\n pass\n\n\n\n## Code to be Reviewed: main.py\n```Code\n## main.py\n\nfrom game import Game\n\ndef main():\n game = Game()\n running = True\n\n while running:\n game.handle_input()\n game.update()\n game.draw()\n\n if game.is_game_over():\n game.restart()\n\nif __name__ == \"__main__\":\n main()\n\n```\n\n## Code Review: main.py\n1. The code is implemented as per the requirements, but the `is_game_over` method is missing. To achieve this, we need to implement the `is_game_over` method in the Game class to check for the game over condition.\n2. The code logic is correct, but the missing `is_game_over` method needs to be added to handle the game over condition.\n3. The existing code follows the \"Data structures and interfaces\" as per the provided class diagram.\n4. All functions are implemented, but the `is_game_over` method is missing in the Game class. To achieve this, we need to implement the `is_game_over` method to handle the game over condition.\n5. All necessary pre-dependencies have been imported.\n6. Methods from other files are not being reused in the current code.\n\n## Actions\n1. Implement the `is_game_over` method in the Game class to handle the game over condition.\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 main.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## main.py\n...\n```\n": "```python\n## main.py\n\nfrom game import Game\n\ndef main():\n game = Game()\n running = True\n\n while running:\n game.handle_input()\n game.update()\n game.draw()\n\n if game.is_game_over(): # Check for game over condition\n game.restart()\n\nif __name__ == \"__main__\":\n main()\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@Author : alexanderwu\n@File : write_review.py\n\"\"\"\nimport asyncio\nfrom typing import List, Literal\n\nfrom metagpt.actions import Action\nfrom metagpt.actions.action_node import ActionNode\n\nREVIEW = ActionNode(\n key=\"Review\",\n expected_type=List[str],\n instruction=\"Act as an experienced reviewer and critically assess the given output. Provide specific and\"\n \" constructive feedback, highlighting areas for improvement and suggesting changes.\",\n example=[\n \"The logic in the function `calculate_total` seems flawed. Shouldn't it consider the discount rate as well?\",\n \"The TODO function is not implemented yet? Should we implement it before commit?\",\n ],\n)\n\nREVIEW_RESULT = ActionNode(\n key=\"ReviewResult\",\n expected_type=Literal[\"LGTM\", \"LBTM\"],\n instruction=\"LGTM/LBTM. If the code is fully implemented, \" \"give a LGTM, otherwise provide a LBTM.\",\n example=\"LBTM\",\n)\n\nNEXT_STEPS = ActionNode(\n key=\"NextSteps\",\n expected_type=str,\n instruction=\"Based on the code review outcome, suggest actionable steps. This can include code changes, \"\n \"refactoring suggestions, or any follow-up tasks.\",\n example=\"\"\"1. Refactor the `process_data` method to improve readability and efficiency.\n2. Cover edge cases in the `validate_user` function.\n3. Implement a the TODO in the `calculate_total` function.\n4. 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 ```\n\"\"\",\n)\n\nWRITE_DRAFT = ActionNode(\n key=\"WriteDraft\",\n expected_type=str,\n instruction=\"Could you write draft code for move function in order to implement it?\",\n example=\"Draft: ...\",\n)\n\n\nWRITE_FUNCTION = ActionNode(\n key=\"WriteFunction\",\n expected_type=str,\n instruction=\"write code for the function not implemented.\",\n example=\"\"\"\n```Code\n...\n```\n\"\"\",\n)\n\n\nREWRITE_CODE = ActionNode(\n key=\"RewriteCode\",\n expected_type=str,\n instruction=\"\"\"rewrite code based on the Review and Actions\"\"\",\n example=\"\"\"\n```python\n## example.py\ndef calculate_total(price, quantity):\n total = price * quantity\n```\n\"\"\",\n)\n\n\nCODE_REVIEW_CONTEXT = \"\"\"\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.\n\n# Context\n## System Design\n{\"Implementation approach\": \"我们将使用HTML、CSS和JavaScript来实现这个单机的响应式2048游戏。为了确保游戏性能流畅和响应式设计,我们会选择使用Vue.js框架,因为它易于上手且适合构建交互式界面。我们还将使用localStorage来记录玩家的最高分。\", \"File list\": [\"index.html\", \"styles.css\", \"main.js\", \"game.js\", \"storage.js\"], \"Data structures and interfaces\": \"classDiagram\\\n class Game {\\\n -board Array\\\n -score Number\\\n -bestScore Number\\\n +constructor()\\\n +startGame()\\\n +move(direction: String)\\\n +getBoard() Array\\\n +getScore() Number\\\n +getBestScore() Number\\\n +setBestScore(score: Number)\\\n }\\\n class Storage {\\\n +getBestScore() Number\\\n +setBestScore(score: Number)\\\n }\\\n class Main {\\\n +init()\\\n +bindEvents()\\\n }\\\n Game --> Storage : uses\\\n Main --> Game : uses\", \"Program call flow\": \"sequenceDiagram\\\n participant M as Main\\\n participant G as Game\\\n participant S as Storage\\\n M->>G: init()\\\n G->>S: getBestScore()\\\n S-->>G: return bestScore\\\n M->>G: bindEvents()\\\n M->>G: startGame()\\\n loop Game Loop\\\n M->>G: move(direction)\\\n G->>S: setBestScore(score)\\\n S-->>G: return\\\n end\", \"Anything UNCLEAR\": \"目前项目要求明确,没有不清楚的地方。\"}\n\n## Tasks\n{\"Required packages\": [\"无需Python包\"], \"Required Other language third-party packages\": [\"vue.js\"], \"Logic Analysis\": [[\"index.html\", \"作为游戏的入口文件和主要的HTML结构\"], [\"styles.css\", \"包含所有的CSS样式,确保游戏界面美观\"], [\"main.js\", \"包含Main类,负责初始化游戏和绑定事件\"], [\"game.js\", \"包含Game类,负责游戏逻辑,如开始游戏、移动方块等\"], [\"storage.js\", \"包含Storage类,用于获取和设置玩家的最高分\"]], \"Task list\": [\"index.html\", \"styles.css\", \"storage.js\", \"game.js\", \"main.js\"], \"Full API spec\": \"\", \"Shared Knowledge\": \"\\'game.js\\' 包含游戏逻辑相关的函数,被 \\'main.js\\' 调用。\", \"Anything UNCLEAR\": \"目前项目要求明确,没有不清楚的地方。\"}\n\n## Code Files\n----- index.html\n\n\n\n \n \n 2048游戏\n \n \n\n\n
\n
2048
\n
\n
\n
分数
\n
{{ score }}
\n
\n
\n
最高分
\n
{{ bestScore }}
\n
\n
\n
\n
\n
\n {{ cell !== 0 ? cell : \\'\\' }}\n
\n
\n
\n \n
\n\n \n \n \n \n\n\n\n----- styles.css\n/* styles.css */\nbody, html {\n margin: 0;\n padding: 0;\n font-family: \\'Arial\\', sans-serif;\n}\n\n#app {\n text-align: center;\n font-size: 18px;\n color: #776e65;\n}\n\nh1 {\n color: #776e65;\n font-size: 72px;\n font-weight: bold;\n margin: 20px 0;\n}\n\n.scores-container {\n display: flex;\n justify-content: center;\n margin-bottom: 20px;\n}\n\n.score-container, .best-container {\n background: #bbada0;\n padding: 10px;\n border-radius: 5px;\n margin: 0 10px;\n min-width: 100px;\n text-align: center;\n}\n\n.score-header, .best-header {\n color: #eee4da;\n font-size: 18px;\n margin-bottom: 5px;\n}\n\n.game-container {\n max-width: 500px;\n margin: 0 auto 20px;\n background: #bbada0;\n padding: 15px;\n border-radius: 10px;\n position: relative;\n}\n\n.grid-row {\n display: flex;\n}\n\n.grid-cell {\n background: #cdc1b4;\n width: 100px;\n height: 100px;\n margin: 5px;\n display: flex;\n justify-content: center;\n align-items: center;\n font-size: 35px;\n font-weight: bold;\n color: #776e65;\n border-radius: 3px;\n}\n\n/* Dynamic classes for different number cells */\n.number-cell-2 {\n background: #eee4da;\n}\n\n.number-cell-4 {\n background: #ede0c8;\n}\n\n.number-cell-8 {\n background: #f2b179;\n color: #f9f6f2;\n}\n\n.number-cell-16 {\n background: #f59563;\n color: #f9f6f2;\n}\n\n.number-cell-32 {\n background: #f67c5f;\n color: #f9f6f2;\n}\n\n.number-cell-64 {\n background: #f65e3b;\n color: #f9f6f2;\n}\n\n.number-cell-128 {\n background: #edcf72;\n color: #f9f6f2;\n}\n\n.number-cell-256 {\n background: #edcc61;\n color: #f9f6f2;\n}\n\n.number-cell-512 {\n background: #edc850;\n color: #f9f6f2;\n}\n\n.number-cell-1024 {\n background: #edc53f;\n color: #f9f6f2;\n}\n\n.number-cell-2048 {\n background: #edc22e;\n color: #f9f6f2;\n}\n\n/* Larger numbers need smaller font sizes */\n.number-cell-1024, .number-cell-2048 {\n font-size: 30px;\n}\n\nbutton {\n background-color: #8f7a66;\n color: #f9f6f2;\n border: none;\n border-radius: 3px;\n padding: 10px 20px;\n font-size: 18px;\n cursor: pointer;\n outline: none;\n}\n\nbutton:hover {\n background-color: #9f8b76;\n}\n\n----- storage.js\n## storage.js\nclass Storage {\n // 获取最高分\n getBestScore() {\n // 尝试从localStorage中获取最高分,如果不存在则默认为0\n const bestScore = localStorage.getItem(\\'bestScore\\');\n return bestScore ? Number(bestScore) : 0;\n }\n\n // 设置最高分\n setBestScore(score) {\n // 将最高分设置到localStorage中\n localStorage.setItem(\\'bestScore\\', score.toString());\n }\n}\n\n\n\n## Code to be Reviewed: game.js\n```Code\n## game.js\nclass Game {\n constructor() {\n this.board = this.createEmptyBoard();\n this.score = 0;\n this.bestScore = 0;\n }\n\n createEmptyBoard() {\n const board = [];\n for (let i = 0; i < 4; i++) {\n board[i] = [0, 0, 0, 0];\n }\n return board;\n }\n\n startGame() {\n this.board = this.createEmptyBoard();\n this.score = 0;\n this.addRandomTile();\n this.addRandomTile();\n }\n\n addRandomTile() {\n let emptyCells = [];\n for (let r = 0; r < 4; r++) {\n for (let c = 0; c < 4; c++) {\n if (this.board[r][c] === 0) {\n emptyCells.push({ r, c });\n }\n }\n }\n if (emptyCells.length > 0) {\n let randomCell = emptyCells[Math.floor(Math.random() * emptyCells.length)];\n this.board[randomCell.r][randomCell.c] = Math.random() < 0.9 ? 2 : 4;\n }\n }\n\n move(direction) {\n // This function will handle the logic for moving tiles\n // in the specified direction and merging them\n // It will also update the score and add a new random tile if the move is successful\n // The actual implementation of this function is complex and would require\n // a significant amount of code to handle all the cases for moving and merging tiles\n // For the purposes of this example, we will not implement the full logic\n // Instead, we will just call addRandomTile to simulate a move\n this.addRandomTile();\n }\n\n getBoard() {\n return this.board;\n }\n\n getScore() {\n return this.score;\n }\n\n getBestScore() {\n return this.bestScore;\n }\n\n setBestScore(score) {\n this.bestScore = score;\n }\n}\n\n```\n\"\"\"\n\n\nCODE_REVIEW_SMALLEST_CONTEXT = \"\"\"\n## Code to be Reviewed: game.js\n```Code\n// game.js\nclass Game {\n constructor() {\n this.board = this.createEmptyBoard();\n this.score = 0;\n this.bestScore = 0;\n }\n\n createEmptyBoard() {\n const board = [];\n for (let i = 0; i < 4; i++) {\n board[i] = [0, 0, 0, 0];\n }\n return board;\n }\n\n startGame() {\n this.board = this.createEmptyBoard();\n this.score = 0;\n this.addRandomTile();\n this.addRandomTile();\n }\n\n addRandomTile() {\n let emptyCells = [];\n for (let r = 0; r < 4; r++) {\n for (let c = 0; c < 4; c++) {\n if (this.board[r][c] === 0) {\n emptyCells.push({ r, c });\n }\n }\n }\n if (emptyCells.length > 0) {\n let randomCell = emptyCells[Math.floor(Math.random() * emptyCells.length)];\n this.board[randomCell.r][randomCell.c] = Math.random() < 0.9 ? 2 : 4;\n }\n }\n\n move(direction) {\n // This function will handle the logic for moving tiles\n // in the specified direction and merging them\n // It will also update the score and add a new random tile if the move is successful\n // The actual implementation of this function is complex and would require\n // a significant amount of code to handle all the cases for moving and merging tiles\n // For the purposes of this example, we will not implement the full logic\n // Instead, we will just call addRandomTile to simulate a move\n this.addRandomTile();\n }\n\n getBoard() {\n return this.board;\n }\n\n getScore() {\n return this.score;\n }\n\n getBestScore() {\n return this.bestScore;\n }\n\n setBestScore(score) {\n this.bestScore = score;\n }\n}\n\n```\n\"\"\"\n\n\nCODE_REVIEW_SAMPLE = \"\"\"\n## Code Review: game.js\n1. The code partially implements the requirements. The `Game` class is missing the full implementation of the `move` method, which is crucial for the game\\'s functionality.\n2. The code logic is not completely correct. The `move` method is not implemented, which means the game cannot process player moves.\n3. The existing code follows the \"Data structures and interfaces\" in terms of class structure but lacks full method implementations.\n4. Not all functions are implemented. The `move` method is incomplete and does not handle the logic for moving and merging tiles.\n5. All necessary pre-dependencies seem to be imported since the code does not indicate the need for additional imports.\n6. The methods from other files (such as `Storage`) are not being used in the provided code snippet, but the class structure suggests that they will be used correctly.\n\n## Actions\n1. Implement the `move` method to handle tile movements and merging. This is a complex task that requires careful consideration of the game\\'s rules and logic. Here is a simplified version of how one might begin to implement the `move` method:\n ```javascript\n move(direction) {\n // Simplified logic for moving tiles up\n if (direction === \\'up\\') {\n for (let col = 0; col < 4; col++) {\n let tiles = this.board.map(row => row[col]).filter(val => val !== 0);\n let merged = [];\n for (let i = 0; i < tiles.length; i++) {\n if (tiles[i] === tiles[i + 1]) {\n tiles[i] *= 2;\n this.score += tiles[i];\n tiles[i + 1] = 0;\n merged.push(i);\n }\n }\n tiles = tiles.filter(val => val !== 0);\n while (tiles.length < 4) {\n tiles.push(0);\n }\n for (let row = 0; row < 4; row++) {\n this.board[row][col] = tiles[row];\n }\n }\n }\n // Additional logic needed for \\'down\\', \\'left\\', \\'right\\'\n // ...\n this.addRandomTile();\n }\n ```\n2. Integrate the `Storage` class methods to handle the best score. This means updating the `startGame` and `setBestScore` methods to use `Storage` for retrieving and setting the best score:\n ```javascript\n startGame() {\n this.board = this.createEmptyBoard();\n this.score = 0;\n this.bestScore = new Storage().getBestScore(); // Retrieve the best score from storage\n this.addRandomTile();\n this.addRandomTile();\n }\n\n setBestScore(score) {\n if (score > this.bestScore) {\n this.bestScore = score;\n new Storage().setBestScore(score); // Set the new best score in storage\n }\n }\n ```\n\n## Code Review Result\nLBTM\n\n```\n\"\"\"\n\n\nWRITE_CODE_NODE = ActionNode.from_children(\"WRITE_REVIEW_NODE\", [REVIEW, REVIEW_RESULT, NEXT_STEPS])\nWRITE_MOVE_NODE = ActionNode.from_children(\"WRITE_MOVE_NODE\", [WRITE_DRAFT, WRITE_FUNCTION])\n\n\nCR_FOR_MOVE_FUNCTION_BY_3 = \"\"\"\nThe move function implementation provided appears to be well-structured and follows a clear logic for moving and merging tiles in the specified direction. However, there are a few potential improvements that could be made to enhance the code:\n\n1. Encapsulation: The logic for moving and merging tiles could be encapsulated into smaller, reusable functions to improve readability and maintainability.\n\n2. Magic Numbers: There are some magic numbers (e.g., 4, 3) used in the loops that could be replaced with named constants for improved readability and easier maintenance.\n\n3. Comments: Adding comments to explain the logic and purpose of each section of the code can improve understanding for future developers who may need to work on or maintain the code.\n\n4. Error Handling: It's important to consider error handling for unexpected input or edge cases to ensure the function behaves as expected in all scenarios.\n\nOverall, the code could benefit from refactoring to improve readability, maintainability, and extensibility. If you would like, I can provide a refactored version of the move function that addresses these considerations.\n\"\"\"\n\n\nclass WriteCodeAN(Action):\n \"\"\"Write a code review for the context.\"\"\"\n\n async def run(self, context):\n self.llm.system_prompt = \"You are an outstanding engineer and can implement any code\"\n return await WRITE_MOVE_NODE.fill(context=context, llm=self.llm, schema=\"json\")\n\n\nasync def main():\n await WriteCodeAN().run(CODE_REVIEW_SMALLEST_CONTEXT)\n\n\nif __name__ == \"__main__\":\n asyncio.run(main())\n\n```\n\n---\nTranslate the code above into Mermaid Sequence Diagram.": "```mermaid\nsequenceDiagram\n participant User\n participant WriteCodeAN\n participant WRITE_MOVE_NODE\n\n User->>WriteCodeAN: Run\n WriteCodeAN->>WRITE_MOVE_NODE: Fill\n WRITE_MOVE_NODE->>WriteCodeAN: Filled\n WriteCodeAN->>User: Return Filled Context\n```",
"You are a function parser.#MSG_SEP#You can convert spoken words into function parameters.#SYSTEM_MSG_END#text_to_image function parameters description:\nparameter `text`: The text used for image conversion.\nparameter `size_type`: size type\n\n---\nExamples:\nIf want you to do `Draw a girl`, return `text_to_image(text=\"Draw a girl\", size_type=\"512x512\")` brief and clear.\nIf want you to do `Draw an apple`, return `text_to_image(text=\"Draw an apple\", size_type=\"512x512\")` brief and clear.\n\n---\n\nRefer to the `text_to_image` function description, and fill in the function parameters according to the example \"I want you to do xx\" in the Examples section.\nNow I want you to do `Draw an apple`, return function parameters in Examples format above, brief and clear.": "`text_to_image(text=\"Draw an apple\", size_type=\"512x512\")`",
- "\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 the Pygame library to create the game interface and handle user input. The game logic will be implemented using Python classes and data structures.\", \"File list\": [\"main.py\", \"game.py\"], \"Data structures and interfaces\": \"classDiagram\\n class Game {\\n -grid: List[List[int]]\\n -score: int\\n -game_over: bool\\n +__init__()\\n +reset_game()\\n +move(direction: str)\\n +is_game_over() bool\\n +get_empty_cells() List[Tuple[int, int]]\\n +add_new_tile()\\n +get_score() int\\n }\\n class UI {\\n -game: Game\\n +__init__(game: Game)\\n +draw_grid()\\n +draw_score()\\n +draw_game_over()\\n +handle_input()\\n }\\n Game --> UI\", \"Program call flow\": \"sequenceDiagram\\n participant M as Main\\n participant G as Game\\n participant U as UI\\n M->>G: reset_game()\\n M->>U: draw_grid()\\n M->>U: draw_score()\\n M->>U: handle_input()\\n U->>G: move(direction)\\n G->>G: add_new_tile()\\n G->>U: draw_grid()\\n G->>U: draw_score()\\n G->>U: draw_game_over()\\n G->>G: is_game_over()\\n G->>G: get_empty_cells()\\n G->>G: get_score()\", \"Anything UNCLEAR\": \"...\"}\n\n## Tasks\n{\"Required Python packages\": [\"pygame==2.0.1\"], \"Required Other language third-party packages\": [\"No third-party dependencies required\"], \"Logic Analysis\": [[\"game.py\", \"Contains Game class and related functions for game logic\"], [\"main.py\", \"Contains main function, initializes the game and UI\"]], \"Task list\": [\"game.py\", \"main.py\"], \"Full API spec\": \"\", \"Shared Knowledge\": \"The game logic will be implemented using Python classes and data structures. The Pygame library will be used to create the game interface and handle user input.\", \"Anything UNCLEAR\": \"...\"}\n\n## Legacy Code\n```Code\n----- main.py\nif __name__ == \"__main__\":\nmain()\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\nfrom typing import List, Tuple\nimport random\n\nclass Game:\n def __init__(self):\n self.grid: List[List[int]] = [[0 for _ in range(4)] for _ in range(4)]\n self.score: int = 0\n self.game_over: bool = False\n\n def reset_game(self):\n self.grid = [[0 for _ in range(4)] for _ in range(4)]\n self.score = 0\n self.game_over = False\n self.add_new_tile()\n self.add_new_tile()\n\n def move(self, direction: str):\n # Implement the logic to move the tiles in the specified direction\n pass\n\n def is_game_over(self) -> bool:\n # Implement the logic to check if the game is over\n pass\n\n def get_empty_cells(self) -> List[Tuple[int, int]]:\n # Implement the logic to get the coordinates of empty cells in the grid\n pass\n\n def add_new_tile(self):\n empty_cells = self.get_empty_cells()\n if empty_cells:\n row, col = random.choice(empty_cells)\n self.grid[row][col] = 2 if random.random() < 0.9 else 4\n\n def get_score(self) -> int:\n return self.score\n```",
+ "\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 the Pygame library to create the game interface and handle user input. The game logic will be implemented using Python classes and data structures.\", \"File list\": [\"main.py\", \"game.py\"], \"Data structures and interfaces\": \"classDiagram\\n class Game {\\n -grid: List[List[int]]\\n -score: int\\n -game_over: bool\\n +__init__()\\n +reset_game()\\n +move(direction: str)\\n +is_game_over() bool\\n +get_empty_cells() List[Tuple[int, int]]\\n +add_new_tile()\\n +get_score() int\\n }\\n class UI {\\n -game: Game\\n +__init__(game: Game)\\n +draw_grid()\\n +draw_score()\\n +draw_game_over()\\n +handle_input()\\n }\\n Game --> UI\", \"Program call flow\": \"sequenceDiagram\\n participant M as Main\\n participant G as Game\\n participant U as UI\\n M->>G: reset_game()\\n M->>U: draw_grid()\\n M->>U: draw_score()\\n M->>U: handle_input()\\n U->>G: move(direction)\\n G->>G: add_new_tile()\\n G->>U: draw_grid()\\n G->>U: draw_score()\\n G->>U: draw_game_over()\\n G->>G: is_game_over()\\n G->>G: get_empty_cells()\\n G->>G: get_score()\", \"Anything UNCLEAR\": \"...\"}\n\n## Tasks\n{\"Required packages\": [\"pygame==2.0.1\"], \"Required Other language third-party packages\": [\"No third-party dependencies required\"], \"Logic Analysis\": [[\"game.py\", \"Contains Game class and related functions for game logic\"], [\"main.py\", \"Contains main function, initializes the game and UI\"]], \"Task list\": [\"game.py\", \"main.py\"], \"Full API spec\": \"\", \"Shared Knowledge\": \"The game logic will be implemented using Python classes and data structures. The Pygame library will be used to create the game interface and handle user input.\", \"Anything UNCLEAR\": \"...\"}\n\n## Legacy Code\n```Code\n----- main.py\nif __name__ == \"__main__\":\nmain()\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\nfrom typing import List, Tuple\nimport random\n\nclass Game:\n def __init__(self):\n self.grid: List[List[int]] = [[0 for _ in range(4)] for _ in range(4)]\n self.score: int = 0\n self.game_over: bool = False\n\n def reset_game(self):\n self.grid = [[0 for _ in range(4)] for _ in range(4)]\n self.score = 0\n self.game_over = False\n self.add_new_tile()\n self.add_new_tile()\n\n def move(self, direction: str):\n # Implement the logic to move the tiles in the specified direction\n pass\n\n def is_game_over(self) -> bool:\n # Implement the logic to check if the game is over\n pass\n\n def get_empty_cells(self) -> List[Tuple[int, int]]:\n # Implement the logic to get the coordinates of empty cells in the grid\n pass\n\n def add_new_tile(self):\n empty_cells = self.get_empty_cells()\n if empty_cells:\n row, col = random.choice(empty_cells)\n self.grid[row][col] = 2 if random.random() < 0.9 else 4\n\n def get_score(self) -> int:\n return self.score\n```",
"\n## context\n\n### Project Name\n\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 \"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\": \"Python\",\n \"Original Requirements\": \"开发一个基于大语言模型与私有知识库的搜索引擎,希望可以基于大语言模型进行搜索总结\",\n \"Project Name\": \"language_model_search\",\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.6, 0.3]\\n \\\"知识库B\\\": [0.4, 0.2]\\n \\\"语言模型搜索C\\\": [0.7, 0.5]\\n \\\"我们的目标产品\\\": [0.8, 0.7]\",\n \"Requirement Analysis\": \"\",\n \"Requirement Pool\": [\n [\n \"P0\",\n \"基于大语言模型的高效搜索功能\"\n ],\n [\n \"P1\",\n \"整合私有知识库,实现个性化搜索结果\"\n ],\n [\n \"P2\",\n \"实现搜索结果的自动总结功能\"\n ]\n ],\n \"UI Design draft\": \"搜索页面简洁明了,搜索结果清晰展示,提供个性化排序和自动总结功能。\",\n \"Anything UNCLEAR\": \"\"\n}\n[/CONTENT]",
"\n## context\nNone\n\n-----\n\n## format example\n[CONTENT]\n{\n \"Project Name\": \"game_2048\"\n}\n[/CONTENT]\n\n## nodes: \": # \"\n- Project Name: # According to the content of \"Original Requirements,\" name the project using snake case style with underline, like 'game_2048' or 'simple_crm.\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 \"Project Name\": \"game_2048\"\n}\n[/CONTENT]",
"\n## context\nCompare 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.\n\n### nodes_output\n{\"Project Name\": {\"value\": \"game snake\", \"requirement\": \"According to the content of \\\"Original Requirements,\\\" name the project using snake case style with underline, like 'game_2048' or 'simple_crm.\"}}\n\n-----\n\n## format example\n[CONTENT]\n{\n \"key1\": \"comment1\",\n \"key2\": \"comment2\",\n \"keyn\": \"commentn\"\n}\n[/CONTENT]\n\n## nodes: \": # \"\n- key1: # the first key name of mismatch key\n- key2: # the second key name of mismatch key\n- keyn: # the last key name of mismatch key\n\n## constraint\nFormat: output wrapped inside [CONTENT][/CONTENT] like format example, nothing else.\n\n## action\nFollow format example's json format, generate output and make sure it follows the format example.\n": "[CONTENT]\n{\n \"Project Name\": \"Use snake case style with underline, like 'game_2048' or 'simple_crm.'\"\n}\n[/CONTENT]",
@@ -278,17 +278,17 @@
"Given an instruction and several choices, decide which choice is most promising. Analyze each choice in detail, then conclude in the last line \"The best choice is {s}\", where s the integer id of the choice.\nChoice 2:\nConsider a flat and stable surface, such as a grassy field or a gym mat.\n": "Choice 2: Considering a flat and stable surface, such as a grassy field or a gym mat, is the most promising option. This choice provides a safe and secure environment for the activity, reducing the risk of injury. A grassy field offers a natural and soft surface, while a gym mat provides cushioning and stability. Both options allow for comfortable movement and can accommodate various physical activities.\n\nThe best choice is 2.",
"Given an instruction and several choices, decide which choice is most promising. Analyze each choice in detail, then conclude in the last line \"The best choice is {s}\", where s the integer id of the choice.\nChoice 4:\nPractice near a wall or with a spotter for safety.\n": "Choice 4: \n\nPractice near a wall or with a spotter for safety.\n\nAnalyzing this choice, practicing near a wall or with a spotter provides a safety measure to prevent accidents or injuries. When practicing a new skill or exercise, having a wall nearby can provide support and stability, reducing the risk of falling or losing balance. Similarly, having a spotter can offer assistance and guidance, ensuring that the practice is done safely and effectively.\n\nThe best choice is 4.",
"### Requirements\n1. Please summarize the latest dialogue based on the reference information (secondary) and dialogue history (primary). Do not include text that is irrelevant to the conversation.\n- The context is for reference only. If it is irrelevant to the user's search request history, please reduce its reference and usage.\n2. If there are citable links in the context, annotate them in the main text in the format [main text](citation link). If there are none in the context, do not write links.\n3. The reply should be graceful, clear, non-repetitive, smoothly written, and of moderate length, in {LANG}.\n\n### Dialogue History (For example)\nA: MLOps competitors\n\n### Current Question (For example)\nA: MLOps competitors\n\n### Current Reply (For example)\n1. Alteryx Designer: etc. if any\n2. Matlab: ditto\n3. IBM SPSS Statistics\n4. RapidMiner Studio\n5. DataRobot AI Platform\n6. Databricks Lakehouse Platform\n7. Amazon SageMaker\n8. Dataiku\n#SYSTEM_MSG_END#\n### Reference Information\nABC cleanser is preferred by many with oily skin.\nL'Oreal is a popular brand with many positive reviews.\n\n### Dialogue History\n\nuser: Which facial cleanser is good for oily skin?\n\n### Current Question\nuser: Which facial cleanser is good for oily skin?\n\n### Current Reply: Based on the information, please write the reply to the Question\n\n\n": "Based on the information provided, ABC cleanser is preferred by many with oily skin. It is a popular choice for individuals with oily skin due to its effectiveness. Additionally, L'Oreal is a well-known brand with many positive reviews, and they offer a range of products suitable for oily skin. Both of these options could be good choices for individuals with oily skin.",
- "\n## context\n{\"Implementation approach\":\"We will use the Pygame library to create the game and handle user input. The game logic will be implemented using Python classes and functions.\",\"File list\":[\"main.py\",\"game.py\"],\"Data structures and interfaces\":\"\\nclassDiagram\\n class Game {\\n -snake: list\\n -food: tuple\\n -score: int\\n +__init__()\\n +update()\\n +draw()\\n +handle_input()\\n +restart()\\n }\\n class Snake {\\n -body: list\\n -direction: str\\n +__init__(start_pos: tuple, length: int, direction: str)\\n +move()\\n +change_direction(new_direction: str)\\n +grow()\\n }\\n Game --> Snake\\n\",\"Program call flow\":\"\\nsequenceDiagram\\n participant M as Main\\n participant G as Game\\n M->>G: initialize game\\n G->>G: update game state\\n G->>G: draw game\\n G->>G: handle user input\\n G->>G: restart game\\n\",\"Anything UNCLEAR\":\"Clarification needed on the scoring system and how the game over condition will be handled.\"}\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 \"pygame==2.0.1\"\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\": \"Clarification needed on the scoring system and how the game over condition will be handled.\"\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 the Pygame library to create the game and handle user input. The game logic will be implemented using Python classes and functions.\",\"File list\":[\"main.py\",\"game.py\"],\"Data structures and interfaces\":\"\\nclassDiagram\\n class Game {\\n -snake: list\\n -food: tuple\\n -score: int\\n +__init__()\\n +update()\\n +draw()\\n +handle_input()\\n +restart()\\n }\\n class Snake {\\n -body: list\\n -direction: str\\n +__init__(start_pos: tuple, length: int, direction: str)\\n +move()\\n +change_direction(new_direction: str)\\n +grow()\\n }\\n Game --> Snake\\n\",\"Program call flow\":\"\\nsequenceDiagram\\n participant M as Main\\n participant G as Game\\n M->>G: initialize game\\n G->>G: update game state\\n G->>G: draw game\\n G->>G: handle user input\\n G->>G: restart game\\n\",\"Anything UNCLEAR\":\"Clarification needed on the scoring system and how the game over condition will be handled.\"}\n\n## Task\n{\"Required Python packages\":[\"pygame==2.0.1\"],\"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\":\"Clarification needed on the scoring system and how the game over condition will be handled.\"}\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\nimport pygame\nimport random\n\nclass Snake:\n def __init__(self, start_pos: tuple, length: int, direction: str):\n self.body = [start_pos]\n self.direction = direction\n self.length = length\n\n def move(self):\n head = self.body[0]\n x, y = head\n if self.direction == 'UP':\n new_head = (x, y - 10)\n elif self.direction == 'DOWN':\n new_head = (x, y + 10)\n elif self.direction == 'LEFT':\n new_head = (x - 10, y)\n elif self.direction == 'RIGHT':\n new_head = (x + 10, y)\n self.body = [new_head] + self.body[:-1]\n\n def change_direction(self, new_direction: str):\n if new_direction in ['UP', 'DOWN', 'LEFT', 'RIGHT']:\n self.direction = new_direction\n\n def grow(self):\n self.length += 1\n self.body.append((0, 0)) # Set default value\n\nclass Game:\n def __init__(self):\n self.snake = Snake((100, 50), 1, 'RIGHT') # Set default value\n self.food = (200, 200) # Set default value\n self.score = 0 # Set default value\n\n def update(self):\n self.snake.move()\n if self.snake.body[0] == self.food:\n self.snake.grow()\n self.food = (random.randrange(0, 400, 10), random.randrange(0, 400, 10)) # Set default value\n\n def draw(self):\n pass # Set default value\n\n def handle_input(self):\n for event in pygame.event.get():\n if event.type == pygame.QUIT:\n pygame.quit()\n quit()\n if event.type == pygame.KEYDOWN:\n if event.key == pygame.K_UP:\n self.snake.change_direction('UP')\n elif event.key == pygame.K_DOWN:\n self.snake.change_direction('DOWN')\n elif event.key == pygame.K_LEFT:\n self.snake.change_direction('LEFT')\n elif event.key == pygame.K_RIGHT:\n self.snake.change_direction('RIGHT')\n\n def restart(self):\n self.snake = Snake((100, 50), 1, 'RIGHT') # Set default value\n self.food = (200, 200) # Set default value\n self.score = 0 # Set default value\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 the Pygame library to create the game and handle user input. The game logic will be implemented using Python classes and functions.\",\"File list\":[\"main.py\",\"game.py\"],\"Data structures and interfaces\":\"\\nclassDiagram\\n class Game {\\n -snake: list\\n -food: tuple\\n -score: int\\n +__init__()\\n +update()\\n +draw()\\n +handle_input()\\n +restart()\\n }\\n class Snake {\\n -body: list\\n -direction: str\\n +__init__(start_pos: tuple, length: int, direction: str)\\n +move()\\n +change_direction(new_direction: str)\\n +grow()\\n }\\n Game --> Snake\\n\",\"Program call flow\":\"\\nsequenceDiagram\\n participant M as Main\\n participant G as Game\\n M->>G: initialize game\\n G->>G: update game state\\n G->>G: draw game\\n G->>G: handle user input\\n G->>G: restart game\\n\",\"Anything UNCLEAR\":\"Clarification needed on the scoring system and how the game over condition will be handled.\"}\n\n## Task\n{\"Required Python packages\":[\"pygame==2.0.1\"],\"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\":\"Clarification needed on the scoring system and how the game over condition will be handled.\"}\n\n## Code Files\n\n\n\n## Code to be Reviewed: game.py\n```Code\n## game.py\n\nimport pygame\nimport random\n\nclass Snake:\n def __init__(self, start_pos: tuple, length: int, direction: str):\n self.body = [start_pos]\n self.direction = direction\n self.length = length\n\n def move(self):\n head = self.body[0]\n x, y = head\n if self.direction == 'UP':\n new_head = (x, y - 10)\n elif self.direction == 'DOWN':\n new_head = (x, y + 10)\n elif self.direction == 'LEFT':\n new_head = (x - 10, y)\n elif self.direction == 'RIGHT':\n new_head = (x + 10, y)\n self.body = [new_head] + self.body[:-1]\n\n def change_direction(self, new_direction: str):\n if new_direction in ['UP', 'DOWN', 'LEFT', 'RIGHT']:\n self.direction = new_direction\n\n def grow(self):\n self.length += 1\n self.body.append((0, 0)) # Set default value\n\nclass Game:\n def __init__(self):\n self.snake = Snake((100, 50), 1, 'RIGHT') # Set default value\n self.food = (200, 200) # Set default value\n self.score = 0 # Set default value\n\n def update(self):\n self.snake.move()\n if self.snake.body[0] == self.food:\n self.snake.grow()\n self.food = (random.randrange(0, 400, 10), random.randrange(0, 400, 10)) # Set default value\n\n def draw(self):\n pass # Set default value\n\n def handle_input(self):\n for event in pygame.event.get():\n if event.type == pygame.QUIT:\n pygame.quit()\n quit()\n if event.type == pygame.KEYDOWN:\n if event.key == pygame.K_UP:\n self.snake.change_direction('UP')\n elif event.key == pygame.K_DOWN:\n self.snake.change_direction('DOWN')\n elif event.key == pygame.K_LEFT:\n self.snake.change_direction('LEFT')\n elif event.key == pygame.K_RIGHT:\n self.snake.change_direction('RIGHT')\n\n def restart(self):\n self.snake = Snake((100, 50), 1, 'RIGHT') # Set default value\n self.food = (200, 200) # Set default value\n self.score = 0 # Set default value\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. The code is implemented as per the requirements. The Snake and Game classes are defined, and the necessary methods for updating, drawing, handling input, and restarting the game are implemented.\n2. The code logic is correct. The Snake class methods for moving, changing direction, and growing the snake are implemented properly. The Game class methods for updating the game state, handling input, and restarting the game are also correctly implemented.\n3. The existing code follows the defined \"Data structures and interfaces\" with the Snake and Game classes and their respective methods.\n4. All necessary functions are implemented as per the requirements.\n5. The necessary pre-dependencies, including the pygame package, are imported.\n6. The methods from other files are not being reused in the current code.\n\n## Actions\nNo actions required.\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 the Pygame library to create the game and handle user input. The game logic will be implemented using Python classes and functions.\",\"File list\":[\"main.py\",\"game.py\"],\"Data structures and interfaces\":\"\\nclassDiagram\\n class Game {\\n -snake: list\\n -food: tuple\\n -score: int\\n +__init__()\\n +update()\\n +draw()\\n +handle_input()\\n +restart()\\n }\\n class Snake {\\n -body: list\\n -direction: str\\n +__init__(start_pos: tuple, length: int, direction: str)\\n +move()\\n +change_direction(new_direction: str)\\n +grow()\\n }\\n Game --> Snake\\n\",\"Program call flow\":\"\\nsequenceDiagram\\n participant M as Main\\n participant G as Game\\n M->>G: initialize game\\n G->>G: update game state\\n G->>G: draw game\\n G->>G: handle user input\\n G->>G: restart game\\n\",\"Anything UNCLEAR\":\"Clarification needed on the scoring system and how the game over condition will be handled.\"}\n\n## Task\n{\"Required Python packages\":[\"pygame==2.0.1\"],\"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\":\"Clarification needed on the scoring system and how the game over condition will be handled.\"}\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: 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 game = Game()\n running = True\n\n while running:\n game.update()\n game.draw()\n game.handle_input()\n\n if game.game_over:\n game.restart()\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 the Pygame library to create the game and handle user input. The game logic will be implemented using Python classes and functions.\",\"File list\":[\"main.py\",\"game.py\"],\"Data structures and interfaces\":\"\\nclassDiagram\\n class Game {\\n -snake: list\\n -food: tuple\\n -score: int\\n +__init__()\\n +update()\\n +draw()\\n +handle_input()\\n +restart()\\n }\\n class Snake {\\n -body: list\\n -direction: str\\n +__init__(start_pos: tuple, length: int, direction: str)\\n +move()\\n +change_direction(new_direction: str)\\n +grow()\\n }\\n Game --> Snake\\n\",\"Program call flow\":\"\\nsequenceDiagram\\n participant M as Main\\n participant G as Game\\n M->>G: initialize game\\n G->>G: update game state\\n G->>G: draw game\\n G->>G: handle user input\\n G->>G: restart game\\n\",\"Anything UNCLEAR\":\"Clarification needed on the scoring system and how the game over condition will be handled.\"}\n\n## Task\n{\"Required Python packages\":[\"pygame==2.0.1\"],\"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\":\"Clarification needed on the scoring system and how the game over condition will be handled.\"}\n\n## Code Files\n\n\n\n## Code to be Reviewed: main.py\n```Code\n## main.py\n\nfrom game import Game\n\ndef main():\n game = Game()\n running = True\n\n while running:\n game.update()\n game.draw()\n game.handle_input()\n\n if game.game_over:\n game.restart()\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. 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. No, the `game_over` condition is not handled in the code. To achieve it, we need to add the implementation of the `game_over` condition in the `Game` class.\n5. Yes, all necessary pre-dependencies have been imported.\n6. Yes, methods from other files are being reused correctly.\n\n## Actions\n4. Implement the `game_over` condition in the `Game` class to handle the game over scenario.\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 the Pygame library to create the game and handle user input. The game logic will be implemented using Python classes and functions.\",\"File list\":[\"main.py\",\"game.py\"],\"Data structures and interfaces\":\"\\nclassDiagram\\n class Game {\\n -snake: list\\n -food: tuple\\n -score: int\\n +__init__()\\n +update()\\n +draw()\\n +handle_input()\\n +restart()\\n }\\n class Snake {\\n -body: list\\n -direction: str\\n +__init__(start_pos: tuple, length: int, direction: str)\\n +move()\\n +change_direction(new_direction: str)\\n +grow()\\n }\\n Game --> Snake\\n\",\"Program call flow\":\"\\nsequenceDiagram\\n participant M as Main\\n participant G as Game\\n M->>G: initialize game\\n G->>G: update game state\\n G->>G: draw game\\n G->>G: handle user input\\n G->>G: restart game\\n\",\"Anything UNCLEAR\":\"Clarification needed on the scoring system and how the game over condition will be handled.\"}\n\n## Task\n{\"Required Python packages\":[\"pygame==2.0.1\"],\"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\":\"Clarification needed on the scoring system and how the game over condition will be handled.\"}\n\n## Code Files\n\n\n\n## Code to be Reviewed: main.py\n```Code\n## main.py\n\nfrom game import Game\n\ndef main():\n game = Game()\n running = True\n\n while running:\n game.update()\n game.draw()\n game.handle_input()\n\n if game.game_over:\n game.restart()\n\nif __name__ == \"__main__\":\n main()\n\n```\n\n## Code Review: main.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. No, the `game_over` condition is not handled in the code. To achieve it, we need to add the implementation of the `game_over` condition in the `Game` class.\n5. Yes, all necessary pre-dependencies have been imported.\n6. Yes, methods from other files are being reused correctly.\n\n## Actions\n4. Implement the `game_over` condition in the `Game` class to handle the game over scenario.\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 main.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## main.py\n...\n```\n": "```Code\n## main.py\n\nfrom game import Game\n\ndef main():\n game = Game()\n running = True\n\n while running:\n game.update()\n game.draw()\n game.handle_input()\n\n if game.game_over():\n game.restart()\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 the Pygame library to create the game and handle user input. The game logic will be implemented using Python classes and functions.\",\"File list\":[\"main.py\",\"game.py\"],\"Data structures and interfaces\":\"\\nclassDiagram\\n class Game {\\n -snake: list\\n -food: tuple\\n -score: int\\n +__init__()\\n +update()\\n +draw()\\n +handle_input()\\n +restart()\\n }\\n class Snake {\\n -body: list\\n -direction: str\\n +__init__(start_pos: tuple, length: int, direction: str)\\n +move()\\n +change_direction(new_direction: str)\\n +grow()\\n }\\n Game --> Snake\\n\",\"Program call flow\":\"\\nsequenceDiagram\\n participant M as Main\\n participant G as Game\\n M->>G: initialize game\\n G->>G: update game state\\n G->>G: draw game\\n G->>G: handle user input\\n G->>G: restart game\\n\",\"Anything UNCLEAR\":\"Clarification needed on the scoring system and how the game over condition will be handled.\"}\n\n## Task\n{\"Required Python packages\":[\"pygame==2.0.1\"],\"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\":\"Clarification needed on the scoring system and how the game over condition will be handled.\"}\n\n## Code Files\n\n\n\n## Code to be Reviewed: main.py\n```Code\n## main.py\n\nfrom game import Game\n\ndef main():\n game = Game()\n running = True\n\n while running:\n game.update()\n game.draw()\n game.handle_input()\n\n if game.game_over():\n game.restart()\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. It initializes the game, updates the game state, draws the game, handles user input, and restarts the game when it's over.\n2. The code logic is correct, and it follows the expected flow of the game.\n3. The existing code follows the \"Data structures and interfaces\" as it uses classes and methods as described in the class diagram.\n4. All necessary functions are implemented as per the requirements.\n5. The necessary pre-dependencies have been imported, as the `pygame` package is imported.\n6. The methods from other files are being reused correctly.\n\n## Actions\npass\n\n## Code Review Result\nLGTM",
- "\n## context\n{'Implementation approach': '我们将使用Python编程语言,并选择合适的开源框架来实现贪吃蛇游戏。我们将分析需求中的难点,并选择合适的开源框架来简化开发流程。', 'File list': ['main.py', 'game.py'], 'Data structures and interfaces': '\\nclassDiagram\\n class Game {\\n -int width\\n -int height\\n -int score\\n -int speed\\n -List snake\\n -Point food\\n +__init__(width: int, height: int, speed: int)\\n +start_game()\\n +change_direction(direction: str)\\n +game_over()\\n +update_snake()\\n +update_food()\\n +check_collision()\\n }\\n class Point {\\n -int x\\n -int y\\n +__init__(x: int, y: int)\\n }\\n Game --> Point\\n', 'Program call flow': '\\nsequenceDiagram\\n participant M as Main\\n participant G as Game\\n M->>G: start_game()\\n M->>G: change_direction(direction)\\n G->>G: update_snake()\\n G->>G: update_food()\\n G->>G: check_collision()\\n G-->>G: game_over()\\n', 'Anything UNCLEAR': ''}\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 \"pygame==2.0.1\"\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 related functions\"\n ],\n [\n \"main.py\",\n \"Contains the main function and imports Game from game.py\"\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\": \"\"\n}\n[/CONTENT]",
+ "\n## context\n{\"Implementation approach\":\"We will use the Pygame library to create the game and handle user input. The game logic will be implemented using Python classes and functions.\",\"File list\":[\"main.py\",\"game.py\"],\"Data structures and interfaces\":\"\\nclassDiagram\\n class Game {\\n -snake: list\\n -food: tuple\\n -score: int\\n +__init__()\\n +update()\\n +draw()\\n +handle_input()\\n +restart()\\n }\\n class Snake {\\n -body: list\\n -direction: str\\n +__init__(start_pos: tuple, length: int, direction: str)\\n +move()\\n +change_direction(new_direction: str)\\n +grow()\\n }\\n Game --> Snake\\n\",\"Program call flow\":\"\\nsequenceDiagram\\n participant M as Main\\n participant G as Game\\n M->>G: initialize game\\n G->>G: update game state\\n G->>G: draw game\\n G->>G: handle user input\\n G->>G: restart game\\n\",\"Anything UNCLEAR\":\"Clarification needed on the scoring system and how the game over condition will be handled.\"}\n\n-----\n\n## format example\n[CONTENT]\n{\n \"Required 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 packages: typing.List[str] # Provide Required 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 packages\": [\n \"pygame==2.0.1\"\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\": \"Clarification needed on the scoring system and how the game over condition will be handled.\"\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 the Pygame library to create the game and handle user input. The game logic will be implemented using Python classes and functions.\",\"File list\":[\"main.py\",\"game.py\"],\"Data structures and interfaces\":\"\\nclassDiagram\\n class Game {\\n -snake: list\\n -food: tuple\\n -score: int\\n +__init__()\\n +update()\\n +draw()\\n +handle_input()\\n +restart()\\n }\\n class Snake {\\n -body: list\\n -direction: str\\n +__init__(start_pos: tuple, length: int, direction: str)\\n +move()\\n +change_direction(new_direction: str)\\n +grow()\\n }\\n Game --> Snake\\n\",\"Program call flow\":\"\\nsequenceDiagram\\n participant M as Main\\n participant G as Game\\n M->>G: initialize game\\n G->>G: update game state\\n G->>G: draw game\\n G->>G: handle user input\\n G->>G: restart game\\n\",\"Anything UNCLEAR\":\"Clarification needed on the scoring system and how the game over condition will be handled.\"}\n\n## Task\n{\"Required packages\":[\"pygame==2.0.1\"],\"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\":\"Clarification needed on the scoring system and how the game over condition will be handled.\"}\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\nimport pygame\nimport random\n\nclass Snake:\n def __init__(self, start_pos: tuple, length: int, direction: str):\n self.body = [start_pos]\n self.direction = direction\n self.length = length\n\n def move(self):\n head = self.body[0]\n x, y = head\n if self.direction == 'UP':\n new_head = (x, y - 10)\n elif self.direction == 'DOWN':\n new_head = (x, y + 10)\n elif self.direction == 'LEFT':\n new_head = (x - 10, y)\n elif self.direction == 'RIGHT':\n new_head = (x + 10, y)\n self.body = [new_head] + self.body[:-1]\n\n def change_direction(self, new_direction: str):\n if new_direction in ['UP', 'DOWN', 'LEFT', 'RIGHT']:\n self.direction = new_direction\n\n def grow(self):\n self.length += 1\n self.body.append((0, 0)) # Set default value\n\nclass Game:\n def __init__(self):\n self.snake = Snake((100, 50), 1, 'RIGHT') # Set default value\n self.food = (200, 200) # Set default value\n self.score = 0 # Set default value\n\n def update(self):\n self.snake.move()\n if self.snake.body[0] == self.food:\n self.snake.grow()\n self.food = (random.randrange(0, 400, 10), random.randrange(0, 400, 10)) # Set default value\n\n def draw(self):\n pass # Set default value\n\n def handle_input(self):\n for event in pygame.event.get():\n if event.type == pygame.QUIT:\n pygame.quit()\n quit()\n if event.type == pygame.KEYDOWN:\n if event.key == pygame.K_UP:\n self.snake.change_direction('UP')\n elif event.key == pygame.K_DOWN:\n self.snake.change_direction('DOWN')\n elif event.key == pygame.K_LEFT:\n self.snake.change_direction('LEFT')\n elif event.key == pygame.K_RIGHT:\n self.snake.change_direction('RIGHT')\n\n def restart(self):\n self.snake = Snake((100, 50), 1, 'RIGHT') # Set default value\n self.food = (200, 200) # Set default value\n self.score = 0 # Set default value\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 the Pygame library to create the game and handle user input. The game logic will be implemented using Python classes and functions.\",\"File list\":[\"main.py\",\"game.py\"],\"Data structures and interfaces\":\"\\nclassDiagram\\n class Game {\\n -snake: list\\n -food: tuple\\n -score: int\\n +__init__()\\n +update()\\n +draw()\\n +handle_input()\\n +restart()\\n }\\n class Snake {\\n -body: list\\n -direction: str\\n +__init__(start_pos: tuple, length: int, direction: str)\\n +move()\\n +change_direction(new_direction: str)\\n +grow()\\n }\\n Game --> Snake\\n\",\"Program call flow\":\"\\nsequenceDiagram\\n participant M as Main\\n participant G as Game\\n M->>G: initialize game\\n G->>G: update game state\\n G->>G: draw game\\n G->>G: handle user input\\n G->>G: restart game\\n\",\"Anything UNCLEAR\":\"Clarification needed on the scoring system and how the game over condition will be handled.\"}\n\n## Task\n{\"Required packages\":[\"pygame==2.0.1\"],\"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\":\"Clarification needed on the scoring system and how the game over condition will be handled.\"}\n\n## Code Files\n\n\n\n## Code to be Reviewed: game.py\n```Code\n## game.py\n\nimport pygame\nimport random\n\nclass Snake:\n def __init__(self, start_pos: tuple, length: int, direction: str):\n self.body = [start_pos]\n self.direction = direction\n self.length = length\n\n def move(self):\n head = self.body[0]\n x, y = head\n if self.direction == 'UP':\n new_head = (x, y - 10)\n elif self.direction == 'DOWN':\n new_head = (x, y + 10)\n elif self.direction == 'LEFT':\n new_head = (x - 10, y)\n elif self.direction == 'RIGHT':\n new_head = (x + 10, y)\n self.body = [new_head] + self.body[:-1]\n\n def change_direction(self, new_direction: str):\n if new_direction in ['UP', 'DOWN', 'LEFT', 'RIGHT']:\n self.direction = new_direction\n\n def grow(self):\n self.length += 1\n self.body.append((0, 0)) # Set default value\n\nclass Game:\n def __init__(self):\n self.snake = Snake((100, 50), 1, 'RIGHT') # Set default value\n self.food = (200, 200) # Set default value\n self.score = 0 # Set default value\n\n def update(self):\n self.snake.move()\n if self.snake.body[0] == self.food:\n self.snake.grow()\n self.food = (random.randrange(0, 400, 10), random.randrange(0, 400, 10)) # Set default value\n\n def draw(self):\n pass # Set default value\n\n def handle_input(self):\n for event in pygame.event.get():\n if event.type == pygame.QUIT:\n pygame.quit()\n quit()\n if event.type == pygame.KEYDOWN:\n if event.key == pygame.K_UP:\n self.snake.change_direction('UP')\n elif event.key == pygame.K_DOWN:\n self.snake.change_direction('DOWN')\n elif event.key == pygame.K_LEFT:\n self.snake.change_direction('LEFT')\n elif event.key == pygame.K_RIGHT:\n self.snake.change_direction('RIGHT')\n\n def restart(self):\n self.snake = Snake((100, 50), 1, 'RIGHT') # Set default value\n self.food = (200, 200) # Set default value\n self.score = 0 # Set default value\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. The code is implemented as per the requirements. The Snake and Game classes are defined, and the necessary methods for updating, drawing, handling input, and restarting the game are implemented.\n2. The code logic is correct. The Snake class methods for moving, changing direction, and growing the snake are implemented properly. The Game class methods for updating the game state, handling input, and restarting the game are also correctly implemented.\n3. The existing code follows the defined \"Data structures and interfaces\" with the Snake and Game classes and their respective methods.\n4. All necessary functions are implemented as per the requirements.\n5. The necessary pre-dependencies, including the pygame package, are imported.\n6. The methods from other files are not being reused in the current code.\n\n## Actions\nNo actions required.\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 the Pygame library to create the game and handle user input. The game logic will be implemented using Python classes and functions.\",\"File list\":[\"main.py\",\"game.py\"],\"Data structures and interfaces\":\"\\nclassDiagram\\n class Game {\\n -snake: list\\n -food: tuple\\n -score: int\\n +__init__()\\n +update()\\n +draw()\\n +handle_input()\\n +restart()\\n }\\n class Snake {\\n -body: list\\n -direction: str\\n +__init__(start_pos: tuple, length: int, direction: str)\\n +move()\\n +change_direction(new_direction: str)\\n +grow()\\n }\\n Game --> Snake\\n\",\"Program call flow\":\"\\nsequenceDiagram\\n participant M as Main\\n participant G as Game\\n M->>G: initialize game\\n G->>G: update game state\\n G->>G: draw game\\n G->>G: handle user input\\n G->>G: restart game\\n\",\"Anything UNCLEAR\":\"Clarification needed on the scoring system and how the game over condition will be handled.\"}\n\n## Task\n{\"Required packages\":[\"pygame==2.0.1\"],\"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\":\"Clarification needed on the scoring system and how the game over condition will be handled.\"}\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: 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 game = Game()\n running = True\n\n while running:\n game.update()\n game.draw()\n game.handle_input()\n\n if game.game_over:\n game.restart()\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 the Pygame library to create the game and handle user input. The game logic will be implemented using Python classes and functions.\",\"File list\":[\"main.py\",\"game.py\"],\"Data structures and interfaces\":\"\\nclassDiagram\\n class Game {\\n -snake: list\\n -food: tuple\\n -score: int\\n +__init__()\\n +update()\\n +draw()\\n +handle_input()\\n +restart()\\n }\\n class Snake {\\n -body: list\\n -direction: str\\n +__init__(start_pos: tuple, length: int, direction: str)\\n +move()\\n +change_direction(new_direction: str)\\n +grow()\\n }\\n Game --> Snake\\n\",\"Program call flow\":\"\\nsequenceDiagram\\n participant M as Main\\n participant G as Game\\n M->>G: initialize game\\n G->>G: update game state\\n G->>G: draw game\\n G->>G: handle user input\\n G->>G: restart game\\n\",\"Anything UNCLEAR\":\"Clarification needed on the scoring system and how the game over condition will be handled.\"}\n\n## Task\n{\"Required packages\":[\"pygame==2.0.1\"],\"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\":\"Clarification needed on the scoring system and how the game over condition will be handled.\"}\n\n## Code Files\n\n\n\n## Code to be Reviewed: main.py\n```Code\n## main.py\n\nfrom game import Game\n\ndef main():\n game = Game()\n running = True\n\n while running:\n game.update()\n game.draw()\n game.handle_input()\n\n if game.game_over:\n game.restart()\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. 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. No, the `game_over` condition is not handled in the code. To achieve it, we need to add the implementation of the `game_over` condition in the `Game` class.\n5. Yes, all necessary pre-dependencies have been imported.\n6. Yes, methods from other files are being reused correctly.\n\n## Actions\n4. Implement the `game_over` condition in the `Game` class to handle the game over scenario.\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 the Pygame library to create the game and handle user input. The game logic will be implemented using Python classes and functions.\",\"File list\":[\"main.py\",\"game.py\"],\"Data structures and interfaces\":\"\\nclassDiagram\\n class Game {\\n -snake: list\\n -food: tuple\\n -score: int\\n +__init__()\\n +update()\\n +draw()\\n +handle_input()\\n +restart()\\n }\\n class Snake {\\n -body: list\\n -direction: str\\n +__init__(start_pos: tuple, length: int, direction: str)\\n +move()\\n +change_direction(new_direction: str)\\n +grow()\\n }\\n Game --> Snake\\n\",\"Program call flow\":\"\\nsequenceDiagram\\n participant M as Main\\n participant G as Game\\n M->>G: initialize game\\n G->>G: update game state\\n G->>G: draw game\\n G->>G: handle user input\\n G->>G: restart game\\n\",\"Anything UNCLEAR\":\"Clarification needed on the scoring system and how the game over condition will be handled.\"}\n\n## Task\n{\"Required packages\":[\"pygame==2.0.1\"],\"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\":\"Clarification needed on the scoring system and how the game over condition will be handled.\"}\n\n## Code Files\n\n\n\n## Code to be Reviewed: main.py\n```Code\n## main.py\n\nfrom game import Game\n\ndef main():\n game = Game()\n running = True\n\n while running:\n game.update()\n game.draw()\n game.handle_input()\n\n if game.game_over:\n game.restart()\n\nif __name__ == \"__main__\":\n main()\n\n```\n\n## Code Review: main.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. No, the `game_over` condition is not handled in the code. To achieve it, we need to add the implementation of the `game_over` condition in the `Game` class.\n5. Yes, all necessary pre-dependencies have been imported.\n6. Yes, methods from other files are being reused correctly.\n\n## Actions\n4. Implement the `game_over` condition in the `Game` class to handle the game over scenario.\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 main.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## main.py\n...\n```\n": "```Code\n## main.py\n\nfrom game import Game\n\ndef main():\n game = Game()\n running = True\n\n while running:\n game.update()\n game.draw()\n game.handle_input()\n\n if game.game_over():\n game.restart()\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 the Pygame library to create the game and handle user input. The game logic will be implemented using Python classes and functions.\",\"File list\":[\"main.py\",\"game.py\"],\"Data structures and interfaces\":\"\\nclassDiagram\\n class Game {\\n -snake: list\\n -food: tuple\\n -score: int\\n +__init__()\\n +update()\\n +draw()\\n +handle_input()\\n +restart()\\n }\\n class Snake {\\n -body: list\\n -direction: str\\n +__init__(start_pos: tuple, length: int, direction: str)\\n +move()\\n +change_direction(new_direction: str)\\n +grow()\\n }\\n Game --> Snake\\n\",\"Program call flow\":\"\\nsequenceDiagram\\n participant M as Main\\n participant G as Game\\n M->>G: initialize game\\n G->>G: update game state\\n G->>G: draw game\\n G->>G: handle user input\\n G->>G: restart game\\n\",\"Anything UNCLEAR\":\"Clarification needed on the scoring system and how the game over condition will be handled.\"}\n\n## Task\n{\"Required packages\":[\"pygame==2.0.1\"],\"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\":\"Clarification needed on the scoring system and how the game over condition will be handled.\"}\n\n## Code Files\n\n\n\n## Code to be Reviewed: main.py\n```Code\n## main.py\n\nfrom game import Game\n\ndef main():\n game = Game()\n running = True\n\n while running:\n game.update()\n game.draw()\n game.handle_input()\n\n if game.game_over():\n game.restart()\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. It initializes the game, updates the game state, draws the game, handles user input, and restarts the game when it's over.\n2. The code logic is correct, and it follows the expected flow of the game.\n3. The existing code follows the \"Data structures and interfaces\" as it uses classes and methods as described in the class diagram.\n4. All necessary functions are implemented as per the requirements.\n5. The necessary pre-dependencies have been imported, as the `pygame` package is imported.\n6. The methods from other files are being reused correctly.\n\n## Actions\npass\n\n## Code Review Result\nLGTM",
+ "\n## context\n{'Implementation approach': '我们将使用Python编程语言,并选择合适的开源框架来实现贪吃蛇游戏。我们将分析需求中的难点,并选择合适的开源框架来简化开发流程。', 'File list': ['main.py', 'game.py'], 'Data structures and interfaces': '\\nclassDiagram\\n class Game {\\n -int width\\n -int height\\n -int score\\n -int speed\\n -List snake\\n -Point food\\n +__init__(width: int, height: int, speed: int)\\n +start_game()\\n +change_direction(direction: str)\\n +game_over()\\n +update_snake()\\n +update_food()\\n +check_collision()\\n }\\n class Point {\\n -int x\\n -int y\\n +__init__(x: int, y: int)\\n }\\n Game --> Point\\n', 'Program call flow': '\\nsequenceDiagram\\n participant M as Main\\n participant G as Game\\n M->>G: start_game()\\n M->>G: change_direction(direction)\\n G->>G: update_snake()\\n G->>G: update_food()\\n G->>G: check_collision()\\n G-->>G: game_over()\\n', 'Anything UNCLEAR': ''}\n\n-----\n\n## format example\n[CONTENT]\n{\n \"Required 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 packages: typing.List[str] # Provide Required 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: