"""
This script enables creating preference datasets using previous conversation logs
(found under eval/runs/). It will load the chosen conversation log, and for each
turn will:
- extract the original agent response from the log file
- generate an alternative candidate response
- ask the human reviewer to select the preferred response
- store the preferences in a .jsonl file
How to Run
----------
Instructions for running the simulation are linked here:
https://github.com/Versebuilding/WalkXR-AI/tree/develop/eval
"""
import sys
from pathlib import Path
import json
import argparse
import requests
import random
project_root = Path(__file__).parent.parent
sys.path.insert(0, str(project_root))
def main():
parser = argparse.ArgumentParser()
parser.add_argument(
"--conversation_log_path",
type=str,
required=True,
help="Path to a raw conversation log .jsonl file under eval/runs/<EVAL_ID>/raw/",
)
parser.add_argument("--temperature", type=float, default=1.2)
parser.add_argument("--timeout", type=int, default=120)
parser.add_argument("--overwrite", action='store_true')
args = parser.parse_args()
# Create file to store results
before, after = args.conversation_log_path.split("/raw/", maxsplit=1)
results_path = before + "/preferences"
results_path = Path(results_path)
results_path.mkdir(parents=True, exist_ok=True)
results_path = results_path / after
if results_path.exists():
if args.overwrite:
# Clear the file
results_path.write_text("")
else:
# Abort if file already exists and --overwrite was not set
print("""\033[1;31mA preference dataset was already created for this conversation log. Please use the \"--overwrite\" flag if you wish to create the preference dataset again.\033[0m""")
return
# Extract json objects from file
turn = 1
with open(args.conversation_log_path, 'r') as log_file, open(results_path, "a", encoding="utf-8") as results_file:
for line in log_file:
obj = json.loads(line)
# Print current turn
print(f"\033[1;32m======== Turn {turn} ========\033[0m")
print()
turn += 1
# Print relevant info to give the human reviewer context
print("\033[1;36mScenario Prompt:\033[0m")
print(obj["scenario_prompt"])
print()
print("\033[1;36mInitial Emotion:\033[0m")
print(obj["initial_emotion"])
print()
print("\033[1;36mDesired AI Behavior:\033[0m")
print(obj["desired_ai_behavior"])
print()
print("\033[1;36mDesired Tone:\033[0m")
print(obj["desired_tone"])
print()
print("\033[1;36mUser Input:\033[0m")
user_input = obj["user_input"]
print(user_input)
print()
# Store other necessary variables
scenario_uid = obj["scenario_id"]
rep = obj["repeat"]
run_id = obj["run_id"]
# Store original agent response
OG_agent_response = obj["agent_response"]
# Generate alternative agent response
ALT_agent_response = ""
response_data = {}
conversation_session_id = f"{scenario_uid}_rep{rep}_{run_id}"
api_success = False
for attempt in range(2):
try:
res = requests.post(
obj["endpoint"],
json={
"user_id": "sim_user",
"session_id": conversation_session_id,
"stage": "demo",
"message": user_input,
"history": [],
"test": {"temperature": args.temperature}
},
timeout=args.timeout,
)
res.raise_for_status()
response_data = res.json()
ALT_agent_response = response_data.get("response_text", "")
api_success = True
break
except requests.exceptions.Timeout as e:
if attempt == 1:
ALT_agent_response = f"API_ERROR: {str(e)}"
print(f"API Timeout: {e}")
else:
print(f"Timeout on attempt {attempt + 1}; retrying once...")
except Exception as e:
ALT_agent_response = f"API_ERROR: {str(e)}"
print(f"API Error: {e}")
break
# Present both responses in a random order to prevent biased review
agent_responses = [OG_agent_response, ALT_agent_response]
# TODO: "None" indicates the default temperature, for future sim log entries it may be beneficial to store the temperature used
temperature = [None, args.temperature]
option_1 = random.randint(0, 1)
option_2 = (option_1 + 1) % 2
present_order = [agent_responses[option_1], agent_responses[option_2]]
present_order_temperature = [temperature[option_1], temperature[option_2]]
print("\033[1;31mAgent Response 1:\033[0m")
print(present_order[0])
print()
print("\033[1;31mAgent Response 2:\033[0m")
print(present_order[1])
print()
# Prompt reviewer to select 1 or 2 depending on which agent_response is better
while True:
try:
best = int(input("\033[1;35mType 1 or 2 to select the best agent response: \033[0m"))
print()
if best == 1 or best == 2:
break
except ValueError:
print()
continue
# Store result as (input, chosen_response, chosen_temperature, rejected_response, rejected_temperature, api_success)
result = {
"user_input": user_input,
"chosen_response": present_order[best - 1],
"chosen_temperature": present_order_temperature[best - 1],
"rejected_response": present_order[best % 2],
"rejected_temperature": present_order_temperature[best % 2],
"api_success": api_success
}
results_file.write(json.dumps(result, ensure_ascii=False) + "\n")
results_file.flush()
if __name__ == "__main__":
main()