import json
import random
from pathlib import Path
from typing import Dict, List, Tuple
def read_memory_data(data_dir: str) -> List[Dict]:
"""
Read all JSONL files under data_dir into a list of dicts, sorted by db_id.
"""
rows = []
for jsonl_file in sorted(Path(data_dir).glob("*.jsonl")):
with open(jsonl_file, "r", encoding="utf-8") as f:
for line in f:
line = line.strip()
if not line:
continue
rows.append(json.loads(line))
rows.sort(key=lambda r: r["db_id"])
return rows
def sample_memories(records: List[Dict], sample_size: int = -1, seed: int = 42) -> List[Dict]:
"""
Sample up to sample_size unique questions (all records for each sampled
question are retained). sample_size == -1 means keep all.
Round-0 cases are excluded before sampling since memory is only used from round 1.
"""
random.seed(seed)
records = [r for r in records if int(r.get("round", 0)) > 0]
question_ids = sorted({r["query_id"] for r in records})
random.shuffle(question_ids)
if sample_size == -1 or sample_size >= len(question_ids):
print("All memory questions sampled.")
sample_size = len(question_ids)
selected_query_ids = set(question_ids[:sample_size])
records_sample = [r for r in records if r["query_id"] in selected_query_ids]
return records_sample
# ---------------------------------------------------------------------------
# Memory construction
# ---------------------------------------------------------------------------
def _build_embedding_text(row: Dict) -> str:
"""
Construct the retrieval key (debate state) from:
1. task x (query)
2. agent previous response z_i^{t-1}
3. previous debate summary
"""
embedding_text = ""
# 1. task
embedding_text += f"{row['query']}\n"
# 2. agent previous response (None for round 0)
prev_response = row.get("prev_response")
if prev_response:
embedding_text += f"{prev_response}\n"
# 3. previous debate summary (None for round 0)
prev_summary = row.get("prev_round_summary")
if prev_summary:
embedding_text += f"{prev_summary}\n"
return embedding_text.strip()
def _build_value_text(row: Dict) -> str:
"""
Construct the retrieval value (prompt context) from:
1. task x (query)
2. correct solution z^*
3. agent current response z_i^t
4. whether agent changed response
5. agent current correctness
All parts are wrapped in XML tags.
"""
value_text = ""
# 1. task
value_text += f"\n{row['query']}\n\n\n"
# 2. correct solution
value_text += f"\n{row['solution']}\n\n\n"
# 3. agent current response
value_text += f"\n{row['current_response']}\n\n\n"
# 4. whether agent changed response (N/A for round 0)
stance_changed = row.get("stance_changed")
if stance_changed is None:
changed_text = "N/A (initial round)"
else:
changed_text = "The agent changed its stance." if stance_changed else "The agent did not change its stance."
value_text += f"\n{changed_text}\n\n\n"
# 5. agent current correctness
is_correct = row.get("is_current_correct", False)
if is_correct:
correct_text = "The response is correct."
else:
correct_text = "The response is incorrect."
value_text += f"\n{correct_text}\n"
return value_text
def _build_meta_data(row: Dict) -> Dict:
"""
Construct the ChromaDB metadata dict.
All values must be ChromaDB-compatible scalars (str / int / float / bool).
"""
prev_consensus = row.get("prev_consensus_ratio")
return {
"category": str(row.get("category", "")),
"question_id": str(row.get("query_id", "")),
"agent_id": str(row.get("agent_id", "")),
"current_round": int(row.get("round", 0)),
"previous_consensus_ratio": float(prev_consensus) if prev_consensus is not None else -1.0,
"response_correct": bool(row.get("is_current_correct", False)),
"final_correct": bool(row.get("is_final_correct", False)),
}
def construct_memory(row: Dict) -> Tuple[str, str, str, Dict]:
"""
Build all four components needed to store one memory entry:
db_id, embedding_text, value_text, meta_data
"""
db_id = row["db_id"]
embedding_text = _build_embedding_text(row)
value_text = _build_value_text(row)
meta_data = _build_meta_data(row)
return db_id, embedding_text, value_text, meta_data
def construct_response_memory(row: Dict) -> Tuple[str, str, Dict]:
"""
Build the (db_id, response_text, metadata) triple for a single memory row's
current_response, to be stored in the response-embedding collection.
"""
response_text = row["current_response"]
meta_data = {
"category": str(row.get("category", "")),
"question_id": str(row.get("query_id", "")),
"agent_id": str(row.get("agent_id", "")),
"round": int(row.get("round", 0)),
"response_correct": bool(row.get("is_current_correct", False)),
"final_correct": bool(row.get("is_final_correct", False)),
}
return row["db_id"], response_text, meta_data