5/15 bug fix
Browse files- README copy.md +0 -137
- app.py +6 -9
- config.json +2 -2
- data/roles/example_world/Lacia-en/icon.png +3 -0
- data/roles/example_world/Trek-en/icon.png +3 -0
- modules/llm/OpenRouter.py +0 -2
README copy.md
DELETED
|
@@ -1,137 +0,0 @@
|
|
| 1 |
-
# BookWorld: Interactive Multi-Agent Story Creation System
|
| 2 |
-
|
| 3 |
-
<div align="center">
|
| 4 |
-
|
| 5 |
-
🖥️ [Project Page](https://bookworld2025.github.io/) | 📃 [Paper](https://arxiv.org/abs/2406.18921) | 🤗 [Demo](https://huggingface.co/spaces/alienet/BookWorld)
|
| 6 |
-
|
| 7 |
-
</div>
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
This is the official implementation of the paper "BOOKWORLD: From Novels to Interactive Agent Societies for Story Creation".
|
| 13 |
-
|
| 14 |
-

|
| 15 |
-
|
| 16 |
-
## Introduction
|
| 17 |
-
|
| 18 |
-
BookWorld is a comprehensive system for social simulation in fictional worlds through multi-agent interactions. The system features:
|
| 19 |
-
|
| 20 |
-
- Scene-based story progression with multiple character agents
|
| 21 |
-
- Continuous updating of agent memories, status, and goals
|
| 22 |
-
- World agent orchestration of the simulation
|
| 23 |
-
- Support for human intervention and control
|
| 24 |
-
- LLM-based story generation and refinement
|
| 25 |
-
|
| 26 |
-
## Setup
|
| 27 |
-
|
| 28 |
-
### Step 1. Clone the repository
|
| 29 |
-
```bash
|
| 30 |
-
git clone https://github.com/your-repo/bookworld.git
|
| 31 |
-
cd bookworld
|
| 32 |
-
```
|
| 33 |
-
|
| 34 |
-
### Step 2. Install dependencies
|
| 35 |
-
```bash
|
| 36 |
-
pip install -r requirements.txt
|
| 37 |
-
```
|
| 38 |
-
|
| 39 |
-
### Step 3. Configure Simulation Settings
|
| 40 |
-
- Update the configuration parameters in `config.json`:
|
| 41 |
-
- `role_llm_name`: LLM model for character roles
|
| 42 |
-
- `world_llm_name`: LLM model for world simulation
|
| 43 |
-
- `config_path`: The path to the experiment
|
| 44 |
-
- `if_save`: Enable/disable saving (1/0)
|
| 45 |
-
- `scene_mode`: Scene progression mode
|
| 46 |
-
- `rounds`: Number of simulation rounds
|
| 47 |
-
- `mode`: Simulation mode ("free" or "script")
|
| 48 |
-
|
| 49 |
-
## Usage
|
| 50 |
-
|
| 51 |
-
### Step 1. Start the server
|
| 52 |
-
```bash
|
| 53 |
-
python server.py
|
| 54 |
-
```
|
| 55 |
-
or
|
| 56 |
-
```bash
|
| 57 |
-
uvicorn server:app --host 127.0.0.1 --port 8000
|
| 58 |
-
```
|
| 59 |
-
|
| 60 |
-
### Step 2. Access the web interface
|
| 61 |
-
Open a browser and navigate to http://localhost:8000.
|
| 62 |
-
|
| 63 |
-
### Step 3. Interact with the system
|
| 64 |
-
- Start/pause/stop story generation
|
| 65 |
-
- View character information and map details
|
| 66 |
-
- Monitor story progression and agent interactions
|
| 67 |
-
- Edit generated content if needed
|
| 68 |
-
|
| 69 |
-
### Step 4. Continue from previous simulation
|
| 70 |
-
Locate the directory of the previous simulation within `/experiment_saves/`, and set its path to the `save_dir` field in `config.json`.
|
| 71 |
-
|
| 72 |
-
## Customization
|
| 73 |
-
### Construct Your Virtual World
|
| 74 |
-
1. Create the roles, map, worldbuilding following the examples given in `/data/`. You can improve the simulation quality by providing background settings about the world in `world_details/` or put character dialogue lines in `role_lines.jsonl`. Additionally, you can place an image named `icon.(png/jpg)` inside the character's folder — this will be used as the avatar displayed in the interface.
|
| 75 |
-
3. Enter the preset path to `preset_path` in `config.json`.
|
| 76 |
-
|
| 77 |
-
### Convert SillyTavern Character Cards to Role Data
|
| 78 |
-
1. Put your character cards in `/data/sillytavern_cards/`.
|
| 79 |
-
2. Run the script. It will convert all the cards into the role data that BookWorld needs.
|
| 80 |
-
```bash
|
| 81 |
-
python convert_sillytavern_cards_to_data.py
|
| 82 |
-
```
|
| 83 |
-
3. Input role codes of all the characters participating in this simulation to `role_agent_codes` in the preset file.
|
| 84 |
-
|
| 85 |
-
## Directory Structure
|
| 86 |
-
|
| 87 |
-
```
|
| 88 |
-
.
|
| 89 |
-
├── data/
|
| 90 |
-
├── frontend/
|
| 91 |
-
│ ├── assets/
|
| 92 |
-
│ ├── css/
|
| 93 |
-
│ └── js/
|
| 94 |
-
├── modules/
|
| 95 |
-
│ ├── db/
|
| 96 |
-
│ ├── llm/
|
| 97 |
-
│ ├── prompt/
|
| 98 |
-
│ ├── main_role_agent.py
|
| 99 |
-
│ └── world_agent.py
|
| 100 |
-
├── experiment_configs/
|
| 101 |
-
├── BookWorld.py
|
| 102 |
-
├── server.py
|
| 103 |
-
├── config.json
|
| 104 |
-
└── index.html
|
| 105 |
-
```
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
## Authors and Citation
|
| 109 |
-
**Authors:** Yiting Ran, Xintao Wang, Tian Qiu,
|
| 110 |
-
Jiaqing Liang, Yanghua Xiao, Deqing Yang.
|
| 111 |
-
|
| 112 |
-
```bibtex
|
| 113 |
-
@misc{ran2025bookworldnovelsinteractiveagent,
|
| 114 |
-
title={BookWorld: From Novels to Interactive Agent Societies for Creative Story Generation},
|
| 115 |
-
author={Yiting Ran and Xintao Wang and Tian Qiu and Jiaqing Liang and Yanghua Xiao and Deqing Yang},
|
| 116 |
-
year={2025},
|
| 117 |
-
eprint={2504.14538},
|
| 118 |
-
archivePrefix={arXiv},
|
| 119 |
-
primaryClass={cs.CL},
|
| 120 |
-
url={https://arxiv.org/abs/2504.14538},
|
| 121 |
-
}
|
| 122 |
-
```
|
| 123 |
-
## License
|
| 124 |
-
|
| 125 |
-
This project is licensed under the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0).
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
## Acknowledgements
|
| 129 |
-
|
| 130 |
-
- Fantasy Map: The background of map panel used in the frontend is from [Free Fantasy Maps](https://freefantasymaps.org/epic-world-cinematic-landscapes/), created by Fantasy Map Maker. This map is free for non-commercial use.
|
| 131 |
-
|
| 132 |
-
## Contact
|
| 133 |
-
|
| 134 |
-
BookWorld is a foundational framework that we aim to continuously optimize and enrich with custom modules. We welcome and greatly appreciate your suggestions and contributions!
|
| 135 |
-
|
| 136 |
-
If you have any suggestions or would like to contribute, please contact us at: [email protected]
|
| 137 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app.py
CHANGED
|
@@ -14,7 +14,7 @@ app = FastAPI()
|
|
| 14 |
default_icon_path = './frontend/assets/images/default-icon.jpg'
|
| 15 |
config = load_json_file('config.json')
|
| 16 |
for key in config:
|
| 17 |
-
if "API_KEY" in key:
|
| 18 |
os.environ[key] = config[key]
|
| 19 |
|
| 20 |
static_file_abspath = os.path.abspath(os.path.join(os.path.dirname(__file__), 'frontend'))
|
|
@@ -25,8 +25,11 @@ class ConnectionManager:
|
|
| 25 |
self.active_connections: dict[str, WebSocket] = {}
|
| 26 |
self.story_tasks: dict[str, asyncio.Task] = {}
|
| 27 |
if True:
|
| 28 |
-
if "preset_path" in config and config["preset_path"]
|
| 29 |
-
|
|
|
|
|
|
|
|
|
|
| 30 |
elif "genre" in config and config["genre"]:
|
| 31 |
genre = config["genre"]
|
| 32 |
preset_path = os.path.join(os.path.dirname(os.path.abspath(__file__)),f"./config/experiment_{genre}.json")
|
|
@@ -202,12 +205,6 @@ async def websocket_endpoint(websocket: WebSocket, client_id: str):
|
|
| 202 |
}
|
| 203 |
})
|
| 204 |
|
| 205 |
-
elif message['type'] == 'request_api_configs':
|
| 206 |
-
await websocket.send_json({
|
| 207 |
-
'type': 'api_configs',
|
| 208 |
-
'data': API_CONFIGS
|
| 209 |
-
})
|
| 210 |
-
|
| 211 |
elif message['type'] == 'api_settings':
|
| 212 |
# 处理API设置
|
| 213 |
settings = message['data']
|
|
|
|
| 14 |
default_icon_path = './frontend/assets/images/default-icon.jpg'
|
| 15 |
config = load_json_file('config.json')
|
| 16 |
for key in config:
|
| 17 |
+
if "API_KEY" in key and config[key]:
|
| 18 |
os.environ[key] = config[key]
|
| 19 |
|
| 20 |
static_file_abspath = os.path.abspath(os.path.join(os.path.dirname(__file__), 'frontend'))
|
|
|
|
| 25 |
self.active_connections: dict[str, WebSocket] = {}
|
| 26 |
self.story_tasks: dict[str, asyncio.Task] = {}
|
| 27 |
if True:
|
| 28 |
+
if "preset_path" in config and config["preset_path"]:
|
| 29 |
+
if os.path.exists(config["preset_path"]):
|
| 30 |
+
preset_path = config["preset_path"]
|
| 31 |
+
else:
|
| 32 |
+
raise ValueError(f"The preset path {config['preset_path']} does not exist.")
|
| 33 |
elif "genre" in config and config["genre"]:
|
| 34 |
genre = config["genre"]
|
| 35 |
preset_path = os.path.join(os.path.dirname(os.path.abspath(__file__)),f"./config/experiment_{genre}.json")
|
|
|
|
| 205 |
}
|
| 206 |
})
|
| 207 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 208 |
elif message['type'] == 'api_settings':
|
| 209 |
# 处理API设置
|
| 210 |
settings = message['data']
|
config.json
CHANGED
|
@@ -2,12 +2,12 @@
|
|
| 2 |
"role_llm_name": "gemini-2",
|
| 3 |
"world_llm_name": "gemini-2",
|
| 4 |
"embedding_model_name":"bge-m3",
|
| 5 |
-
"preset_path":"./experiment_presets/
|
| 6 |
"if_save": 0,
|
| 7 |
"scene_mode": 1,
|
| 8 |
"rounds": 10,
|
| 9 |
"save_dir": "",
|
| 10 |
-
"mode": "
|
| 11 |
|
| 12 |
"OPENAI_API_KEY":"",
|
| 13 |
"GEMINI_API_KEY":"",
|
|
|
|
| 2 |
"role_llm_name": "gemini-2",
|
| 3 |
"world_llm_name": "gemini-2",
|
| 4 |
"embedding_model_name":"bge-m3",
|
| 5 |
+
"preset_path":"./experiment_presets/experiment_icefire.json",
|
| 6 |
"if_save": 0,
|
| 7 |
"scene_mode": 1,
|
| 8 |
"rounds": 10,
|
| 9 |
"save_dir": "",
|
| 10 |
+
"mode": "free",
|
| 11 |
|
| 12 |
"OPENAI_API_KEY":"",
|
| 13 |
"GEMINI_API_KEY":"",
|
data/roles/example_world/Lacia-en/icon.png
ADDED
|
|
Git LFS Details
|
data/roles/example_world/Trek-en/icon.png
ADDED
|
|
Git LFS Details
|
modules/llm/OpenRouter.py
CHANGED
|
@@ -38,8 +38,6 @@ class OpenRouter(BaseLLM):
|
|
| 38 |
self.initialize_message()
|
| 39 |
self.user_message(text)
|
| 40 |
response = self.get_response(temperature = temperature)
|
| 41 |
-
print("In",self.count_token(text))
|
| 42 |
-
print("Out", self.count_token(response))
|
| 43 |
self.in_token += self.count_token(text)
|
| 44 |
self.out_token += self.count_token(response)
|
| 45 |
return response
|
|
|
|
| 38 |
self.initialize_message()
|
| 39 |
self.user_message(text)
|
| 40 |
response = self.get_response(temperature = temperature)
|
|
|
|
|
|
|
| 41 |
self.in_token += self.count_token(text)
|
| 42 |
self.out_token += self.count_token(response)
|
| 43 |
return response
|