上一篇文章对项目的核心文件的功能做了单独介绍。接下来我们就具体看看各个模块的功能实现。这一篇先看前端环境模拟部分。
之前介绍编译运行的文章中提到,启动和常用功能大概有下面几项:
- 🚀 启动 environment 服务
- 启动 simulation 服务
- 运行和保存 Simulation
- 重放之前保存的 simulation
- 演示 simulation
以上 5 点可以分为两类。一部分是真正调用 LLM 大模型生成对话和记忆,然后检索规划,最终保存整个过程。另一部分是把前面运行的过程保存下来的场景,进行重放和演示。
功能介绍
今天重点学习重放 replay,启动 environment 前端服务。前端环境是一个基于 Django 框架的应用。
python
cd environment/frontend_server
python manage.py runserver
打开 http://localhost:8000/
浏览器显示"Your environment server is up and running,"说明运行成功了。
打开 http://localhost:8000/replay/July1_the_ville_isabella_maria_klaus-step-3-20/1/
开始 replay。

整个页面分为三部分:
- 地图及角色动画展示,基于 Phaser 3.0 实现。这部分后面有时间单独介绍。
- 时间和回放控制
- 👯 角色列表和角色详情,核心信息展示。
代码
plain
# http://localhost:8000/replay/July1_the_ville_isabella_maria_klaus-step-3-20/1/
# 拆解
http://localhost:8000/ # base url
replay/ # command
July1_the_ville_isabella_maria_klaus-step-3-20/ # simulation 文件
1/ # 步骤,可以手动指定第几步
启动 django 服务,指定默认配置文件目录为 frontend_server.settings
python
def main():
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'frontend_server.settings')
try:
from django.core.management import execute_from_command_line
execute_from_command_line(sys.argv)
if __name__ == '__main__':
main()
环境配置文件
python
# 需要跟随 django 启动的模块
INSTALLED_APPS = [
# ...
'translator',
'corsheaders',
'storages',
]
# 项目的 URL 路由入口模块
ROOT_URLCONF = 'frontend_server.urls'
# 静态资源与媒体文件
STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(os.path.dirname(BASE_DIR), "static_root")
STATICFILES_DIRS = (
os.path.join(BASE_DIR, "static_dirs"),
)
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(os.path.dirname(BASE_DIR), "media_root")
解析刚才的 url,匹配到 replay/
,转到 translator.replay
方法
bash
from translator import views as translator_views
urlpatterns = [
url(r'^$', translator_views.landing, name='landing'),
url(r'^simulator_home$', translator_views.home, name='home'),
url(r'^demo/(?P<sim_code>[\w-]+)/(?P<step>[\w-]+)/(?P<play_speed>[\w-]+)/$', translator_views.demo, name='demo'),
url(r'^replay/(?P<sim_code>[\w-]+)/(?P<step>[\w-]+)/$', translator_views.replay, name='replay'),
url(r'^replay_persona_state/(?P<sim_code>[\w-]+)/(?P<step>[\w-]+)/(?P<persona_name>[\w-]+)/$', translator_views.replay_persona_state, name='replay_persona_state'),
url(r'^process_environment/$', translator_views.process_environment, name='process_environment'),
url(r'^update_environment/$', translator_views.update_environment, name='update_environment'),
url(r'^path_tester/$', translator_views.path_tester, name='path_tester'),
url(r'^path_tester_update/$', translator_views.path_tester_update, name='path_tester_update'),
path('admin/', admin.site.urls),
]
解析
python
def replay(request, sim_code, step):
persona_names_set = set() # 人物角色列表
for i in find_filenames(f"storage/{sim_code}/personas", ""): # 遍历存储的 replay 文件,找到人物角色
persona_names_set.add(x)
for i in find_filenames(f"storage/{sim_code}/environment", ".json"):
file_count += [int(x.split(".")[0])]
curr_json = f'storage/{sim_code}/environment/{str(max(file_count))}.json'
with open(curr_json) as json_file:
persona_init_pos_dict = json.load(json_file)
for key, val in persona_init_pos_dict.items():
if key in persona_names_set:
persona_init_pos += [[key, val["x"], val["y"]]]
context = {"sim_code": sim_code, # 模拟文件名:July1_the_ville_isabella_maria_klaus-step-3-20
"step": step, # 对应 environment/ 下面的 *.json 文件,游戏里面的 10 秒代表一步,也就是 10s 一个 json 文件
"persona_names": persona_names, # 对应 personas 目录下的名字
"persona_init_pos": persona_init_pos, # 角色 初始的 位置
"mode": "replay"} # 控制是否显示 play/pause 按钮
template = "home/home.html"
return render(request, template, context)
replay
函数读取的 json 文件:
plain
July1_the_ville_isabella_maria_klaus-step-3-20
├── environment
│ ├── 0.json # 角色坐标文件
│ ├── 1.json
│ ├── 10.json
│ ├── 100.json
│ ├── 1000.json
│ ├── 1001.json
│ ├── ...
├── personas
│ ├── Isabella\ Rodriguez # 角色文件
│ │ └── bootstrap_memory
│ │ ├── associative_memory # 关联记忆
│ │ │ ├── embeddings.json # embedding 矢量存储
│ │ │ ├── kw_strength.json # ?
│ │ │ └── nodes.json # 记忆节点
│ │ ├── scratch.json # 角色的基础信息
│ │ └── spatial_memory.json # 空间环境记忆
│ ├── Klaus\ Mueller
│ │ └── bootstrap_memory
│ │ ├── associative_memory
│ │ │ ├── embeddings.json
│ │ │ ├── kw_strength.json
│ │ │ └── nodes.json
│ │ ├── scratch.json
│ │ └── spatial_memory.json
│ └── Maria\ Lopez
│ └── bootstrap_memory
│ ├── associative_memory
│ │ ├── embeddings.json
│ │ ├── kw_strength.json
│ │ └── nodes.json
│ ├── scratch.json
│ └── spatial_memory.json
└── reverie
└── meta.json # simulator 基本信息
接下来 render 开始渲染界面 home/home.html
,并且引入了 phaser 和 main_script.html
脚本。
bash
<script src='https://cdn.jsdelivr.net/npm/phaser@3.55.2/dist/phaser.js'></script>
{% include 'home/main_script.html' %}
main_script.html
主要负责游戏界面处理,同时负责和 django 服务通信。
bash
function preload() {
}
function create() {}
function update(time, delta) {
if (phase == "process") {
var retrieve_xobj = new XMLHttpRequest();
retrieve_xobj.overrideMimeType("application/json");
retrieve_xobj.open('POST', "{% url 'process_environment' %}", true);
retrieve_xobj.send(json);
// Finally, we update the phase variable to start the "udpate" process.
// Now that we sent all persona locations to the backend server, we need
// to wait until the backend determines what the personas will do next.
phase = "update";
} else if (phase == "update") {
var update_xobj = new XMLHttpRequest();
update_xobj.overrideMimeType("application/json");
update_xobj.open('POST', "{% url 'update_environment' %}", true);
update_xobj.send(JSON.stringify({"step": step, "sim_code": sim_code }));
phase = "execute";
} else {
phase = "process";
}
}
调用 process_environment
和update_environment
接口处理数据
python
def process_environment(request):
"""
<FRONTEND to BACKEND>
This sends the frontend visual world information to the backend server.
It does this by writing the current environment representation to
"storage/environment.json" file.
"""
data = json.loads(request.body)
step = data["step"]
sim_code = data["sim_code"]
environment = data["environment"]
with open(f"storage/{sim_code}/environment/{step}.json", "w") as outfile:
outfile.write(json.dumps(environment, indent=2))
return HttpResponse("received")
def update_environment(request):
"""
<BACKEND to FRONTEND>
This sends the backend computation of the persona behavior to the frontend
visual server.
It does this by reading the new movement information from
"storage/movement.json" file.
"""
data = json.loads(request.body)
step = data["step"]
sim_code = data["sim_code"]
response_data = {"<step>": -1}
if (check_if_file_exists(f"storage/{sim_code}/movement/{step}.json")):
with open(f"storage/{sim_code}/movement/{step}.json") as json_file:
response_data = json.load(json_file)
response_data["<step>"] = step
return JsonResponse(response_data)
Agent 数据结构
关于一个 Agent 的描述
