Commit 5719d102 by nzy

First commit

parents
__pycache__/*
js/node_modules/*
\ No newline at end of file
import argparse
from synthesis import synthesis, synthesis_1
from lib import check_holes, check_hole_1, create_sketch, create_dest
import argparse
import os
import tempfile
import asyncio
def doc_template(fun_name):
return f"""\
/**
* Description
* @param bot - The bot in mineflayer
*/
async function {fun_name}(bot)
"""
def sketch_template(fun_name):
return f"""\
async function {fun_name}(bot) {{
}}
"""
def input_content(content):
editor = "code --wait"
with tempfile.NamedTemporaryFile(mode='r+t', suffix=".js") as f:
f.write(content)
f.flush()
os.system(f"{editor} {f.name}")
f.seek(0)
content = f.read()
return content
def sketch(fun_name):
code = input_content(sketch_template(fun_name))
create_sketch(fun_name, code)
def hole(fun_name):
doc = input_content(doc_template(fun_name))
create_dest(fun_name, doc)
async def main():
parser = argparse.ArgumentParser(description="A script for anplc.")
subparsers = parser.add_subparsers(dest="command")
sketch_parser = subparsers.add_parser('sketch')
sketch_parser.add_argument('fun_name', nargs='?', default=None, help="Name of the sketch to edit.")
hole_parser = subparsers.add_parser('hole')
hole_parser.add_argument('fun_name', nargs='?', default=None, help="Name of the hole to edit.")
syn_parser = subparsers.add_parser('synthesis')
syn_parser.add_argument('fun_name', nargs='?', default=None, help="Name of the function to synthesis.")
syn_parser.add_argument('--all', action='store_true', help="Compile all functions.")
args = parser.parse_args()
if args.command == "sketch":
sketch(args.fun_name)
elif args.command == "hole":
hole(args.fun_name)
elif args.command == "synthesis":
if args.all:
print("Synthesis all holes")
holes = check_holes()
if len(holes) != 0:
print("Please edit these holes:")
print(holes)
else:
await synthesis()
elif args.fun_name:
print(f"Synthesis hole: {args.fun_name}")
holes = check_hole_1(args.fun_name)
if len(holes) != 0:
print("Please edit these holes:")
print(holes)
else:
await synthesis_1(args.fun_name)
else:
parser.print_help()
# This is for windows only
# https://stackoverflow.com/questions/45600579/asyncio-event-loop-is-closed-when-getting-loop
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
asyncio.run(main())
\ No newline at end of file
module.exports = {
"env": {
"browser": true,
"es2021": true,
"node": true,
},
"overrides": [
{
"env": {
"node": true
},
"files": [
".eslintrc.{js,cjs}"
],
"parserOptions": {
"sourceType": "script"
}
}
],
"parserOptions": {
"ecmaVersion": "latest",
"sourceType": "module"
},
"rules": {
"no-undef": "error",
}
}
{
"name": "js",
"version": "1.0.0",
"description": "Minecraft Funcion Lib",
"author": "",
"license": "MIT"
}
from sketch_analysis import find_holes
from pathlib import Path
import json
dest_lib = Path("./descriptions")
fun_lib = Path("./js")
def check_holes():
holes_without_docs = []
for dest_path in dest_lib.glob("*.json"):
with open(dest_path, "r") as f:
dest = json.load(f)
if dest["state"] == "x" and len(dest["doc"]) == 0:
holes_without_docs.append(dest_path.stem)
return holes_without_docs
def check_hole_1(fun_name):
holes_without_docs = []
with open(dest_lib / f"{fun_name}.json", "r") as f:
dependencies = json.load(f)["dependencies"]
for dependency in dependencies:
with open(dest_lib / f"{dependency}.json", "r") as f:
dest = json.load(f)
if dest["state"] == "x" and len(dest["doc"]) == 0:
holes_without_docs.append(dependency)
return holes_without_docs
def create_dest(name, doc):
path = dest_lib / f"{name}.json"
dest = {"state": "x", "dependencies": [], "doc": doc}
with open(path, "w") as f:
json.dump(dest, f)
def create_sketch(fun_name, code):
with open(fun_lib / f"{fun_name}.js", "w") as f:
f.write(code)
holes = find_holes(fun_name)
final_code = []
for hole in holes:
final_code.append(f"const {{ {hole} }} = require('./{hole}.js');")
final_code.append(code)
final_code.append(f"exports.{fun_name} = {fun_name};")
final_code = "\n".join(final_code)
with open(fun_lib / f"{fun_name}.js", "w") as f:
f.write(final_code)
dest = {"state": "v", "dependencies": list(holes), "doc": ""}
with open(dest_lib / f"{fun_name}.json", "w") as f:
json.dump(dest, f)
for hole in holes:
path = dest_lib / f"{hole}.json"
if not path.exists():
create_dest(hole, "")
return holes
if __name__ == "__main__":
create_sketch("test")
\ No newline at end of file
import openai
import re
openai.api_key = "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"
openai.proxy = {
"http":"http://127.0.0.1:7890",
"https":"http://127.0.0.1:7890"
}
def msg(role: str, content: str): return {"role": role, "content": content}
pattern = re.compile(r"```javascript(.+?)```", flags=re.DOTALL)
def extract_code(response):
return re.search(pattern, response).group(1)
def mk_prompt(doc):
return f"""You are a helpful assistant that writes Mineflayer javascript code to complete any function specified by me.
You should then respond to me with
Code:
1) Write an async function following the description.
2) Your function will be reused for building more complex functions. Therefore, you should make it generic and reusable. You should not make strong assumption about the inventory (as it may be changed at a later time), and therefore you should always check whether you have the required items before using them. If not, you should first collect the required items and reuse the above useful programs.
3) Anything defined outside a function will be ignored, define all your variables inside your functions.
4) Call \`bot.chat\` to show the intermediate progress.
5) Do not write infinite loops or recursive functions.
6) Do not use \`bot.on\` or \`bot.once\` to register event listeners. You definitely do not need them.
Here is my comments.
{doc}
You should only respond in the format as described below:
RESPONSE FORMAT:
Explain: ...
Plan:
1) ...
2) ...
3) ...
Code:
\`\`\`javascript
// helper functions (only if needed, try to avoid them)
...
// main function after the helper functions
async function functionName(bot, ...) {{
\/\/ ...
}}
\`\`\``;"""
async def llm_code(fun_name, doc):
print(f"Synthesis {fun_name}")
config = {"model": "gpt-3.5-turbo", "n": 1, "temperature": 0}
prompt = mk_prompt(doc)
response = await openai.ChatCompletion.acreate(messages=[msg("user", prompt)], **config)
content = response["choices"][0]["message"]["content"]
print(f"# {fun_name}")
print(content)
print()
return extract_code(content)
# ANPL 4 MineCraft
## Intro
目录下有两个文件夹
- js 代码文件夹,用来存放sketch与生成的code
- descriptions 描述文件夹,用来存放function的描述与function的状态
## Installation
此项目依赖
### eslint
安装方法: `npm install -g eslint`
测试方法: `eslint ./js/test.js` test.js是任一javascript文件,观察报错信息即可。如果代码中有未定义的函数,则会报错,否则无输出。
### openai
安装方法: `pip install openai`
## Usage
主要有3条命令
- `python anplc.py sketch {name}` 增加/修改某个草图
- `python anplc.py hole {name}` 修改某个hole的描述
- `python anplc.py synthesis --all | {name}` 生成hole,all会生成library中全部的hole,指定name的话生成指定的hole。
### Sketch
sketch命令会弹出vscode,按照vscode内模板写完sketch后,关闭文件即可。
与原版anpl不同的是,此处必须为hole赋予函数名。例如
```javascript
function test() {
this_is_a_hole()
this_is_another_hole()
}
```
### Hole
输入后,anplc会创建两个hole `this_is_a_hole, this_is_another_hole`
如果此时直接使用synthesis,则会提示必须先编辑hole才能生成。
`python anplc.py hole this_is_a_hole`
`python anplc.py hole this_is_another_hole`
编辑hole时,需要修改Description的部分,与函数的签名,也可以添加参数的具体描述。
### Synthesis
编辑hole的描述后,即可以开始使用`python anplc.py synthesis --all | {name}`生成。
### Import
生成的代码都在js文件夹中,可以使用一个repl bot调用,如目录下run.js所示。
## Others
非windows系统下,请注释掉`anplc.py`第85行`asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())`
此行为windows系统特有问题。
对于openai库的配置,请修改`llm.py`的开头,修改api_key与代理。
\ No newline at end of file
openai
\ No newline at end of file
const mineflayer = require('mineflayer')
const repl = require('repl')
if (process.argv.length !== 3) {
console.log("Usage: node run.js <host>")
}
const bot = mineflayer.createBot({
host: 'localhost',
port: process.argv[2],
username: 'repl',
auth: 'offline'
})
function mk_path(fun_name) {
return `./js/${fun_name}.js`
}
async function load(fun_name) {
const m = await import(mk_path(fun_name))
return m[fun_name];
}
bot.on('login', () => {
const r = repl.start('>')
r.context.bot = bot
r.context.load = load
r.on('exit', () => {
bot.end()
})
})
\ No newline at end of file
import subprocess
import json
import re
def find_holes(fun_name):
cmd = ['eslint', '--format=json', f"./js/{fun_name}.js"]
result = subprocess.run(cmd, capture_output=True, text=True, shell=True)
holes = set()
eslint_output = json.loads(result.stdout)
for item in eslint_output:
messages = item.get('messages', [])
for message in messages:
if message.get('ruleId') == 'no-undef':
m = re.search(r"'(.*?)' is not defined", message.get("message"))
if m:
holes.add(m.group(1))
return holes
if __name__ == "__main__":
import argparse
argparser = argparse.ArgumentParser()
argparser.add_argument('fun_name', help="The name of the sketch")
args = argparser.parse_args()
print(find_holes(args.funname))
\ No newline at end of file
from llm import llm_code
from pathlib import Path
import json
import asyncio
dest_lib = Path("./descriptions")
fun_lib = Path("./js")
async def synthesis_1(fun_name):
dest_path = dest_lib / f"{fun_name}.json"
fun_path = fun_lib / f"{fun_name}.js"
with open(dest_path, "r") as f:
dest = json.load(f)
if dest["state"] == "x":
code = await llm_code(fun_name, dest["doc"])
code += f"\nexports.{fun_name} = {fun_name};"
with open(fun_path, "w") as f:
f.write(code)
dest["state"] = "v"
with open(dest_path, "w") as f:
json.dump(dest, f)
else:
print(f"{fun_name} has already been completed.")
async def synthesis():
names = []
for dest_path in dest_lib.glob("*.json"):
with open(dest_path, "r") as f:
dest = json.load(f)
if dest["state"] == "x":
names.append(dest_path.stem)
await asyncio.gather(*[synthesis_1(fun) for fun in names])
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment