refine action node and add some experiment

This commit is contained in:
geekan 2023-12-21 11:03:13 +08:00
parent c9b785e453
commit f4198dc111
5 changed files with 968 additions and 30 deletions

View file

@ -9,7 +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
from typing import Any, Dict, Generic, List, Optional, Tuple, Type, TypeVar
from typing import Any, Dict, List, Optional, Tuple, Type
from pydantic import BaseModel, create_model, root_validator, validator
from tenacity import retry, stop_after_attempt, wait_random_exponential
@ -19,10 +19,11 @@ from metagpt.logs import logger
from metagpt.provider.postprecess.llm_output_postprecess import llm_output_postprecess
from metagpt.utils.common import OutputParser, general_after_log
CONSTRAINT = """
- Language: Please use the same language as the user input.
- Format: output wrapped inside [CONTENT][/CONTENT] as format example, nothing else.
"""
TAG = "CONTENT"
LANGUAGE_CONSTRAINT = "Language: Please use the same language as the user input."
FORMAT_CONSTRAINT = f"Format: output wrapped inside [{TAG}][/{TAG}] like format example, nothing else."
SIMPLE_TEMPLATE = """
## context
@ -33,28 +34,25 @@ SIMPLE_TEMPLATE = """
## format example
{example}
## nodes: "<node>: <type> # <comment>"
## nodes: "<node>: <type> # <instruction>"
{instruction}
## constraint
{constraint}
## action
Fill in the above nodes based on the format example.
Follow instructions of nodes, generate output and make sure it follows the format example.
"""
def dict_to_markdown(d, prefix="##", kv_sep="\n", postfix="\n"):
def dict_to_markdown(d, prefix="- ", kv_sep="\n", postfix="\n"):
markdown_str = ""
for key, value in d.items():
markdown_str += f"{prefix}{key}{kv_sep}{value}{postfix}"
return markdown_str
T = TypeVar("T")
class ActionNode(Generic[T]):
class ActionNode:
"""ActionNode is a tree of nodes."""
mode: str
@ -69,7 +67,7 @@ class ActionNode(Generic[T]):
expected_type: Type # such as str / int / float etc.
# context: str # everything in the history.
instruction: str # the instructions should be followed.
example: T # example for In Context-Learning.
example: Any # example for In Context-Learning.
# Action Output
content: str
@ -80,7 +78,7 @@ class ActionNode(Generic[T]):
key: str,
expected_type: Type,
instruction: str,
example: T,
example: Any,
content: str = "",
children: dict[str, "ActionNode"] = None,
):
@ -183,11 +181,11 @@ class ActionNode(Generic[T]):
return node_dict
def compile_to(self, i: Dict, schema) -> str:
def compile_to(self, i: Dict, schema, kv_sep) -> str:
if schema == "json":
return json.dumps(i, indent=4)
elif schema == "markdown":
return dict_to_markdown(i)
return dict_to_markdown(i, kv_sep=kv_sep)
else:
return str(i)
@ -196,26 +194,26 @@ class ActionNode(Generic[T]):
return text
if schema == "json":
return f"[{tag}]\n" + text + f"\n[/{tag}]"
else:
else: # markdown
return f"[{tag}]\n" + text + f"\n[/{tag}]"
def _compile_f(self, schema, mode, tag, format_func) -> str:
def _compile_f(self, schema, mode, tag, format_func, kv_sep) -> str:
nodes = self.to_dict(format_func=format_func, mode=mode)
text = self.compile_to(nodes, schema)
text = self.compile_to(nodes, schema, kv_sep)
return self.tagging(text, schema, tag)
def compile_instruction(self, schema="raw", mode="children", tag="") -> str:
def compile_instruction(self, schema="markdown", mode="children", tag="") -> str:
"""compile to raw/json/markdown template with all/root/children nodes"""
format_func = lambda i: f"{i.expected_type} # {i.instruction}"
return self._compile_f(schema, mode, tag, format_func)
return self._compile_f(schema, mode, tag, format_func, kv_sep=": ")
def compile_example(self, schema="raw", mode="children", tag="") -> str:
def compile_example(self, schema="json", mode="children", tag="") -> str:
"""compile to raw/json/markdown examples with all/root/children nodes"""
# 这里不能使用f-string因为转译为str后再json.dumps会额外加上引号无法作为有效的example
# 错误示例:"File list": "['main.py', 'const.py', 'game.py']", 注意这里值不是list而是str
format_func = lambda i: i.example
return self._compile_f(schema, mode, tag, format_func)
return self._compile_f(schema, mode, tag, format_func, kv_sep="\n")
def compile(self, context, schema="json", mode="children", template=SIMPLE_TEMPLATE) -> str:
"""
@ -228,9 +226,16 @@ class ActionNode(Generic[T]):
# FIXME: json instruction会带来格式问题"Project name": "web_2048 # 项目名称使用下划线",
# compile example暂时不支持markdown
self.instruction = self.compile_instruction(schema="markdown", mode=mode)
self.example = self.compile_example(schema=schema, tag="CONTENT", mode=mode)
self.example = self.compile_example(schema=schema, tag=TAG, mode=mode)
# nodes = ", ".join(self.to_dict(mode=mode).keys())
constraints = [LANGUAGE_CONSTRAINT, FORMAT_CONSTRAINT]
constraint = "\n".join(constraints)
prompt = template.format(
context=context, example=self.example, instruction=self.instruction, constraint=CONSTRAINT
context=context,
example=self.example,
instruction=self.instruction,
constraint=constraint,
)
return prompt
@ -253,7 +258,7 @@ class ActionNode(Generic[T]):
output_class = self.create_model_class(output_class_name, output_data_mapping)
if schema == "json":
parsed_data = llm_output_postprecess(output=content, schema=output_class.schema(), req_key="[/CONTENT]")
parsed_data = llm_output_postprecess(output=content, schema=output_class.schema(), req_key=f"[/{TAG}]")
else: # using markdown parser
parsed_data = OutputParser.parse_data_with_mapping(content, output_data_mapping)

View file

@ -0,0 +1,591 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
@Author : alexanderwu
@File : write_review.py
"""
import asyncio
from typing import List
from metagpt.actions import Action
from metagpt.actions.action_node import ActionNode
REVIEW = ActionNode(
key="Review",
expected_type=List[str],
instruction="Act as an experienced reviewer and critically assess the given output. Provide specific and"
" constructive feedback, highlighting areas for improvement and suggesting changes.",
example=[
"The logic in the function `calculate_total` seems flawed. Shouldn't it consider the discount rate as well?",
"The TODO function is not implemented yet? Should we implement it before commit?",
],
)
LGTM = ActionNode(
key="LGTM",
expected_type=str,
instruction="LGTM/LBTM. If the code is fully implemented, "
"give a LGTM (Looks Good To Me), otherwise provide a LBTM (Looks Bad To Me).",
example="LBTM",
)
ACTIONS = ActionNode(
key="Actions",
expected_type=str,
instruction="Based on the code review outcome, suggest actionable steps. This can include code changes, "
"refactoring suggestions, or any follow-up tasks.",
example="""1. Refactor the `process_data` method to improve readability and efficiency.
2. Cover edge cases in the `validate_user` function.
3. Implement a the TODO in the `calculate_total` function.
4. Fix the `handle_events` method to update the game state only if a move is successful.
```python
def handle_events(self):
for event in pygame.event.get():
if event.type == pygame.QUIT:
return False
if event.type == pygame.KEYDOWN:
moved = False
if event.key == pygame.K_UP:
moved = self.game.move('UP')
elif event.key == pygame.K_DOWN:
moved = self.game.move('DOWN')
elif event.key == pygame.K_LEFT:
moved = self.game.move('LEFT')
elif event.key == pygame.K_RIGHT:
moved = self.game.move('RIGHT')
if moved:
# Update the game state only if a move was successful
self.render()
return True
```
""",
)
WRITE_DRAFT = ActionNode(
key="WriteDraft",
expected_type=str,
instruction="Could you write draft code for move function in order to implement it?",
example="Draft: ...",
)
WRITE_MOVE_FUNCTION = ActionNode(
key="WriteFunction",
expected_type=str,
instruction="write code for the function not implemented.",
example="""
```Code
...
```
""",
)
REWRITE_CODE = ActionNode(
key="RewriteCode",
expected_type=str,
instruction="""rewrite code based on the Review and Actions""",
example="""
```python
## example.py
def calculate_total(price, quantity):
total = price * quantity
```
""",
)
CODE_REVIEW_CONTEXT = """
# System
Role: 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.
Language: 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.
# Context
## System Design
{"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\
class Game {\
-board Array\
-score Number\
-bestScore Number\
+constructor()\
+startGame()\
+move(direction: String)\
+getBoard() Array\
+getScore() Number\
+getBestScore() Number\
+setBestScore(score: Number)\
}\
class Storage {\
+getBestScore() Number\
+setBestScore(score: Number)\
}\
class Main {\
+init()\
+bindEvents()\
}\
Game --> Storage : uses\
Main --> Game : uses", "Program call flow": "sequenceDiagram\
participant M as Main\
participant G as Game\
participant S as Storage\
M->>G: init()\
G->>S: getBestScore()\
S-->>G: return bestScore\
M->>G: bindEvents()\
M->>G: startGame()\
loop Game Loop\
M->>G: move(direction)\
G->>S: setBestScore(score)\
S-->>G: return\
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": "目前项目要求明确,没有不清楚的地方。"}
## Code Files
----- index.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>2048游戏</title>
<link rel="stylesheet" href="styles.css">
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script>
</head>
<body>
<div id="app">
<h1>2048</h1>
<div class="scores-container">
<div class="score-container">
<div class="score-header">分数</div>
<div>{{ score }}</div>
</div>
<div class="best-container">
<div class="best-header">最高分</div>
<div>{{ bestScore }}</div>
</div>
</div>
<div class="game-container">
<div v-for="(row, rowIndex) in board" :key="rowIndex" class="grid-row">
<div v-for="(cell, cellIndex) in row" :key="cellIndex" class="grid-cell" :class="\'number-cell-\' + cell">
{{ cell !== 0 ? cell : \'\' }}
</div>
</div>
</div>
<button @click="startGame" aria-label="开始新游戏">新游戏</button>
</div>
<script src="storage.js"></script>
<script src="game.js"></script>
<script src="main.js"></script>
<script src="app.js"></script>
</body>
</html>
----- styles.css
/* styles.css */
body, html {
margin: 0;
padding: 0;
font-family: \'Arial\', sans-serif;
}
#app {
text-align: center;
font-size: 18px;
color: #776e65;
}
h1 {
color: #776e65;
font-size: 72px;
font-weight: bold;
margin: 20px 0;
}
.scores-container {
display: flex;
justify-content: center;
margin-bottom: 20px;
}
.score-container, .best-container {
background: #bbada0;
padding: 10px;
border-radius: 5px;
margin: 0 10px;
min-width: 100px;
text-align: center;
}
.score-header, .best-header {
color: #eee4da;
font-size: 18px;
margin-bottom: 5px;
}
.game-container {
max-width: 500px;
margin: 0 auto 20px;
background: #bbada0;
padding: 15px;
border-radius: 10px;
position: relative;
}
.grid-row {
display: flex;
}
.grid-cell {
background: #cdc1b4;
width: 100px;
height: 100px;
margin: 5px;
display: flex;
justify-content: center;
align-items: center;
font-size: 35px;
font-weight: bold;
color: #776e65;
border-radius: 3px;
}
/* Dynamic classes for different number cells */
.number-cell-2 {
background: #eee4da;
}
.number-cell-4 {
background: #ede0c8;
}
.number-cell-8 {
background: #f2b179;
color: #f9f6f2;
}
.number-cell-16 {
background: #f59563;
color: #f9f6f2;
}
.number-cell-32 {
background: #f67c5f;
color: #f9f6f2;
}
.number-cell-64 {
background: #f65e3b;
color: #f9f6f2;
}
.number-cell-128 {
background: #edcf72;
color: #f9f6f2;
}
.number-cell-256 {
background: #edcc61;
color: #f9f6f2;
}
.number-cell-512 {
background: #edc850;
color: #f9f6f2;
}
.number-cell-1024 {
background: #edc53f;
color: #f9f6f2;
}
.number-cell-2048 {
background: #edc22e;
color: #f9f6f2;
}
/* Larger numbers need smaller font sizes */
.number-cell-1024, .number-cell-2048 {
font-size: 30px;
}
button {
background-color: #8f7a66;
color: #f9f6f2;
border: none;
border-radius: 3px;
padding: 10px 20px;
font-size: 18px;
cursor: pointer;
outline: none;
}
button:hover {
background-color: #9f8b76;
}
----- storage.js
## storage.js
class Storage {
// 获取最高分
getBestScore() {
// 尝试从localStorage中获取最高分如果不存在则默认为0
const bestScore = localStorage.getItem(\'bestScore\');
return bestScore ? Number(bestScore) : 0;
}
// 设置最高分
setBestScore(score) {
// 将最高分设置到localStorage中
localStorage.setItem(\'bestScore\', score.toString());
}
}
## Code to be Reviewed: game.js
```Code
## game.js
class Game {
constructor() {
this.board = this.createEmptyBoard();
this.score = 0;
this.bestScore = 0;
}
createEmptyBoard() {
const board = [];
for (let i = 0; i < 4; i++) {
board[i] = [0, 0, 0, 0];
}
return board;
}
startGame() {
this.board = this.createEmptyBoard();
this.score = 0;
this.addRandomTile();
this.addRandomTile();
}
addRandomTile() {
let emptyCells = [];
for (let r = 0; r < 4; r++) {
for (let c = 0; c < 4; c++) {
if (this.board[r][c] === 0) {
emptyCells.push({ r, c });
}
}
}
if (emptyCells.length > 0) {
let randomCell = emptyCells[Math.floor(Math.random() * emptyCells.length)];
this.board[randomCell.r][randomCell.c] = Math.random() < 0.9 ? 2 : 4;
}
}
move(direction) {
// This function will handle the logic for moving tiles
// in the specified direction and merging them
// It will also update the score and add a new random tile if the move is successful
// The actual implementation of this function is complex and would require
// a significant amount of code to handle all the cases for moving and merging tiles
// For the purposes of this example, we will not implement the full logic
// Instead, we will just call addRandomTile to simulate a move
this.addRandomTile();
}
getBoard() {
return this.board;
}
getScore() {
return this.score;
}
getBestScore() {
return this.bestScore;
}
setBestScore(score) {
this.bestScore = score;
}
}
```
"""
CODE_REVIEW_SMALLEST_CONTEXT = """
## Code to be Reviewed: game.js
```Code
// game.js
class Game {
constructor() {
this.board = this.createEmptyBoard();
this.score = 0;
this.bestScore = 0;
}
createEmptyBoard() {
const board = [];
for (let i = 0; i < 4; i++) {
board[i] = [0, 0, 0, 0];
}
return board;
}
startGame() {
this.board = this.createEmptyBoard();
this.score = 0;
this.addRandomTile();
this.addRandomTile();
}
addRandomTile() {
let emptyCells = [];
for (let r = 0; r < 4; r++) {
for (let c = 0; c < 4; c++) {
if (this.board[r][c] === 0) {
emptyCells.push({ r, c });
}
}
}
if (emptyCells.length > 0) {
let randomCell = emptyCells[Math.floor(Math.random() * emptyCells.length)];
this.board[randomCell.r][randomCell.c] = Math.random() < 0.9 ? 2 : 4;
}
}
move(direction) {
// This function will handle the logic for moving tiles
// in the specified direction and merging them
// It will also update the score and add a new random tile if the move is successful
// The actual implementation of this function is complex and would require
// a significant amount of code to handle all the cases for moving and merging tiles
// For the purposes of this example, we will not implement the full logic
// Instead, we will just call addRandomTile to simulate a move
this.addRandomTile();
}
getBoard() {
return this.board;
}
getScore() {
return this.score;
}
getBestScore() {
return this.bestScore;
}
setBestScore(score) {
this.bestScore = score;
}
}
```
"""
CODE_REVIEW_SAMPLE = """
## Code Review: game.js
1. 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.
2. The code logic is not completely correct. The `move` method is not implemented, which means the game cannot process player moves.
3. The existing code follows the "Data structures and interfaces" in terms of class structure but lacks full method implementations.
4. Not all functions are implemented. The `move` method is incomplete and does not handle the logic for moving and merging tiles.
5. All necessary pre-dependencies seem to be imported since the code does not indicate the need for additional imports.
6. 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.
## Actions
1. 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:
```javascript
move(direction) {
// Simplified logic for moving tiles up
if (direction === \'up\') {
for (let col = 0; col < 4; col++) {
let tiles = this.board.map(row => row[col]).filter(val => val !== 0);
let merged = [];
for (let i = 0; i < tiles.length; i++) {
if (tiles[i] === tiles[i + 1]) {
tiles[i] *= 2;
this.score += tiles[i];
tiles[i + 1] = 0;
merged.push(i);
}
}
tiles = tiles.filter(val => val !== 0);
while (tiles.length < 4) {
tiles.push(0);
}
for (let row = 0; row < 4; row++) {
this.board[row][col] = tiles[row];
}
}
}
// Additional logic needed for \'down\', \'left\', \'right\'
// ...
this.addRandomTile();
}
```
2. 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:
```javascript
startGame() {
this.board = this.createEmptyBoard();
this.score = 0;
this.bestScore = new Storage().getBestScore(); // Retrieve the best score from storage
this.addRandomTile();
this.addRandomTile();
}
setBestScore(score) {
if (score > this.bestScore) {
this.bestScore = score;
new Storage().setBestScore(score); // Set the new best score in storage
}
}
```
## Code Review Result
LBTM
```
"""
WRITE_CODE_NODE = ActionNode.from_children("WRITE_REVIEW_NODE", [REVIEW, LGTM, ACTIONS])
WRITE_MOVE_NODE = ActionNode.from_children("WRITE_MOVE_NODE", [WRITE_DRAFT, WRITE_MOVE_FUNCTION])
CR_FOR_MOVE_FUNCTION_BY_3 = """
The 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:
1. Encapsulation: The logic for moving and merging tiles could be encapsulated into smaller, reusable functions to improve readability and maintainability.
2. 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.
3. 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.
4. 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.
Overall, 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.
"""
class WriteCodeAN(Action):
"""Write a code review for the context."""
async def run(self, context):
self.llm.system_prompt = "You are an outstanding engineer and can implement any code"
return await WRITE_MOVE_FUNCTION.fill(context=context, llm=self.llm, schema="json")
# return await WRITE_CODE_NODE.fill(context=context, llm=self.llm, schema="markdown")
async def main():
await WriteCodeAN().run(CODE_REVIEW_SMALLEST_CONTEXT)
if __name__ == "__main__":
asyncio.run(main())

View file

@ -31,8 +31,7 @@ WRITE_REVIEW_NODE = ActionNode.from_children("WRITE_REVIEW_NODE", [REVIEW, LGTM]
class WriteReview(Action):
"""This class allows LLM to further mine noteworthy details based on specific "##TOPIC"(discussion topic) and
"##RECORD" (discussion records), thereby deepening the discussion."""
"""Write a review for the given context."""
async def run(self, context):
return await WRITE_REVIEW_NODE.fill(context=context, llm=self.llm, schema="markdown")
return await WRITE_REVIEW_NODE.fill(context=context, llm=self.llm, schema="json")

View file

@ -158,7 +158,8 @@ class OutputParser:
@classmethod
def parse_data_with_mapping(cls, data, mapping):
data = cls.extract_content(text=data)
if "[CONTENT]" in data:
data = cls.extract_content(text=data)
block_dict = cls.parse_blocks(data)
parsed_data = {}
for block, content in block_dict.items():

View file

@ -0,0 +1,342 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
@Time : 2023/5/11 14:45
@Author : alexanderwu
@File : test_llm.py
"""
import pytest
from metagpt.llm import LLM
CODE_REVIEW_SMALLEST_CONTEXT = """
## game.js
```Code
// game.js
class Game {
constructor() {
this.board = this.createEmptyBoard();
this.score = 0;
this.bestScore = 0;
}
createEmptyBoard() {
const board = [];
for (let i = 0; i < 4; i++) {
board[i] = [0, 0, 0, 0];
}
return board;
}
startGame() {
this.board = this.createEmptyBoard();
this.score = 0;
this.addRandomTile();
this.addRandomTile();
}
addRandomTile() {
let emptyCells = [];
for (let r = 0; r < 4; r++) {
for (let c = 0; c < 4; c++) {
if (this.board[r][c] === 0) {
emptyCells.push({ r, c });
}
}
}
if (emptyCells.length > 0) {
let randomCell = emptyCells[Math.floor(Math.random() * emptyCells.length)];
this.board[randomCell.r][randomCell.c] = Math.random() < 0.9 ? 2 : 4;
}
}
move(direction) {
// This function will handle the logic for moving tiles
// in the specified direction and merging them
// It will also update the score and add a new random tile if the move is successful
// The actual implementation of this function is complex and would require
// a significant amount of code to handle all the cases for moving and merging tiles
// For the purposes of this example, we will not implement the full logic
// Instead, we will just call addRandomTile to simulate a move
this.addRandomTile();
}
getBoard() {
return this.board;
}
getScore() {
return this.score;
}
getBestScore() {
return this.bestScore;
}
setBestScore(score) {
this.bestScore = score;
}
}
```
"""
MOVE_DRAFT = """
## move function draft
```javascript
move(direction) {
let moved = false;
switch (direction) {
case 'up':
for (let c = 0; c < 4; c++) {
for (let r = 1; r < 4; r++) {
if (this.board[r][c] !== 0) {
let row = r;
while (row > 0 && this.board[row - 1][c] === 0) {
this.board[row - 1][c] = this.board[row][c];
this.board[row][c] = 0;
row--;
moved = true;
}
if (row > 0 && this.board[row - 1][c] === this.board[row][c]) {
this.board[row - 1][c] *= 2;
this.board[row][c] = 0;
this.score += this.board[row - 1][c];
moved = true;
}
}
}
}
break;
case 'down':
// Implement logic for moving tiles down
// Similar to the 'up' case but iterating in reverse order
// and checking for merging in the opposite direction
break;
case 'left':
// Implement logic for moving tiles left
// Similar to the 'up' case but iterating over columns first
// and checking for merging in the opposite direction
break;
case 'right':
// Implement logic for moving tiles right
// Similar to the 'up' case but iterating over columns in reverse order
// and checking for merging in the opposite direction
break;
}
if (moved) {
this.addRandomTile();
}
}
```
"""
FUNCTION_TO_MERMAID_CLASS = """
## context
```
class UIDesign(Action):
#Class representing the UI Design action.
def __init__(self, name, context=None, llm=None):
super().__init__(name, context, llm) # 需要调用LLM进一步丰富UI设计的prompt
@parse
def parse_requirement(self, context: str):
#Parse UI Design draft from the context using regex.
pattern = r"## UI Design draft.*?\n(.*?)## Anything UNCLEAR"
return context, pattern
@parse
def parse_ui_elements(self, context: str):
#Parse Selected Elements from the context using regex.
pattern = r"## Selected Elements.*?\n(.*?)## HTML Layout"
return context, pattern
@parse
def parse_css_code(self, context: str):
pattern = r"```css.*?\n(.*?)## Anything UNCLEAR"
return context, pattern
@parse
def parse_html_code(self, context: str):
pattern = r"```html.*?\n(.*?)```"
return context, pattern
async def draw_icons(self, context, *args, **kwargs):
#Draw icons using SDEngine.
engine = SDEngine()
icon_prompts = self.parse_ui_elements(context)
icons = icon_prompts.split("\n")
icons = [s for s in icons if len(s.strip()) > 0]
prompts_batch = []
for icon_prompt in icons:
# fixme: 添加icon lora
prompt = engine.construct_payload(icon_prompt + ".<lora:WZ0710_AW81e-3_30e3b128d64T32_goon0.5>")
prompts_batch.append(prompt)
await engine.run_t2i(prompts_batch)
logger.info("Finish icon design using StableDiffusion API")
async def _save(self, css_content, html_content):
save_dir = CONFIG.workspace_path / "resources" / "codes"
if not os.path.exists(save_dir):
os.makedirs(save_dir, exist_ok=True)
# Save CSS and HTML content to files
css_file_path = save_dir / "ui_design.css"
html_file_path = save_dir / "ui_design.html"
with open(css_file_path, "w") as css_file:
css_file.write(css_content)
with open(html_file_path, "w") as html_file:
html_file.write(html_content)
async def run(self, requirements: list[Message], *args, **kwargs) -> ActionOutput:
#Run the UI Design action.
# fixme: update prompt (根据需求细化prompt
context = requirements[-1].content
ui_design_draft = self.parse_requirement(context=context)
# todo: parse requirements str
prompt = PROMPT_TEMPLATE.format(context=ui_design_draft, format_example=FORMAT_EXAMPLE)
logger.info(prompt)
ui_describe = await self._aask_v1(prompt, "ui_design", OUTPUT_MAPPING)
logger.info(ui_describe.content)
logger.info(ui_describe.instruct_content)
css = self.parse_css_code(context=ui_describe.content)
html = self.parse_html_code(context=ui_describe.content)
await self._save(css_content=css, html_content=html)
await self.draw_icons(ui_describe.content)
return ui_describe
```
-----
## format example
[CONTENT]
{
"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 "
}
[/CONTENT]
## nodes: "<node>: <type> # <comment>"
- ClassView: <class 'str'> # Generate the mermaid class diagram corresponding to source code in "context."
## constraint
- Language: Please use the same language as the user input.
- Format: output wrapped inside [CONTENT][/CONTENT] as format example, nothing else.
## action
Fill in the above nodes(ClassView) based on the format example.
"""
MOVE_FUNCTION = """
## move function implementation
```javascript
move(direction) {
let moved = false;
switch (direction) {
case 'up':
for (let c = 0; c < 4; c++) {
for (let r = 1; r < 4; r++) {
if (this.board[r][c] !== 0) {
let row = r;
while (row > 0 && this.board[row - 1][c] === 0) {
this.board[row - 1][c] = this.board[row][c];
this.board[row][c] = 0;
row--;
moved = true;
}
if (row > 0 && this.board[row - 1][c] === this.board[row][c]) {
this.board[row - 1][c] *= 2;
this.board[row][c] = 0;
this.score += this.board[row - 1][c];
moved = true;
}
}
}
}
break;
case 'down':
for (let c = 0; c < 4; c++) {
for (let r = 2; r >= 0; r--) {
if (this.board[r][c] !== 0) {
let row = r;
while (row < 3 && this.board[row + 1][c] === 0) {
this.board[row + 1][c] = this.board[row][c];
this.board[row][c] = 0;
row++;
moved = true;
}
if (row < 3 && this.board[row + 1][c] === this.board[row][c]) {
this.board[row + 1][c] *= 2;
this.board[row][c] = 0;
this.score += this.board[row + 1][c];
moved = true;
}
}
}
}
break;
case 'left':
for (let r = 0; r < 4; r++) {
for (let c = 1; c < 4; c++) {
if (this.board[r][c] !== 0) {
let col = c;
while (col > 0 && this.board[r][col - 1] === 0) {
this.board[r][col - 1] = this.board[r][col];
this.board[r][col] = 0;
col--;
moved = true;
}
if (col > 0 && this.board[r][col - 1] === this.board[r][col]) {
this.board[r][col - 1] *= 2;
this.board[r][col] = 0;
this.score += this.board[r][col - 1];
moved = true;
}
}
}
}
break;
case 'right':
for (let r = 0; r < 4; r++) {
for (let c = 2; c >= 0; c--) {
if (this.board[r][c] !== 0) {
let col = c;
while (col < 3 && this.board[r][col + 1] === 0) {
this.board[r][col + 1] = this.board[r][col];
this.board[r][col] = 0;
col++;
moved = true;
}
if (col < 3 && this.board[r][col + 1] === this.board[r][col]) {
this.board[r][col + 1] *= 2;
this.board[r][col] = 0;
this.score += this.board[r][col + 1];
moved = true;
}
}
}
}
break;
}
if (moved) {
this.addRandomTile();
}
}
```
"""
@pytest.fixture()
def llm():
return LLM()
@pytest.mark.asyncio
async def test_llm_code_review(llm):
choices = [
"Please review the move function code above. Should it be refactor?",
"Please implement the move function",
"Please write a draft for the move function in order to implement it",
]
# prompt = CODE_REVIEW_SMALLEST_CONTEXT+ "\n\n" + MOVE_DRAFT + "\n\n" + choices[1]
# rsp = await llm.aask(prompt)
prompt = CODE_REVIEW_SMALLEST_CONTEXT + "\n\n" + MOVE_FUNCTION + "\n\n" + choices[0]
prompt = FUNCTION_TO_MERMAID_CLASS
_ = await llm.aask(prompt)
# if __name__ == "__main__":
# pytest.main([__file__, "-s"])