This commit is contained in:
2026-09-27 23:06:24 +08:00
parent 2d4f9230b1
commit 22450e5bb3
40 changed files with 3040 additions and 697 deletions
+42 -21
View File
@@ -378,14 +378,15 @@ class CitizenGenerator:
)
user_text += f"\n\nTARGET SEGMENTS (distribute citizens across these):\n{seg_text}"
response = await self.llm.generate(
system=system_prompt,
user=user_text,
json_mode=True,
max_tokens=4000,
)
data = parse_json(response)
response = await self.llm.generate(
system=system_prompt,
user=user_text,
json_mode=True,
max_tokens=4000,
purpose="cast_generate",
)
data = parse_json(response, purpose="居民名单生成")
if isinstance(data, dict) and "citizens" in data:
cast = data["citizens"]
elif isinstance(data, list):
@@ -400,25 +401,26 @@ class CitizenGenerator:
and cast
and not any(self._has_cjk(str(c.get("name", ""))) for c in cast)
):
logger.warning("Cast generation returned non-CJK names despite zh locale, retrying. Names: %s", [c.get('name') for c in cast])
logger.warning("中文模式下居民姓名仍非中文,将重试。当前姓名:%s", [c.get('name') for c in cast])
retry_user = user_text + (
"\n\n[STRICT REMINDER] All names, roles, personality hooks, and "
"stances MUST be written in Chinese characters. Do NOT use English."
)
response = await self.llm.generate(
system=system_prompt,
user=retry_user,
json_mode=True,
max_tokens=3000,
)
data = parse_json(response)
response = await self.llm.generate(
system=system_prompt,
user=retry_user,
json_mode=True,
max_tokens=3000,
purpose="cast_generate_retry",
)
data = parse_json(response, purpose="居民名单生成重试")
if isinstance(data, dict) and "citizens" in data:
cast = data["citizens"]
elif isinstance(data, list):
cast = data
if len(cast) < count:
logger.warning("Cast generation returned %d/%d citizens, padding", len(cast), count)
logger.warning("居民生成仅返回 %d/%d 人,将使用默认居民补齐", len(cast), count)
while len(cast) < count:
idx = len(cast)
if self._locale == "zh":
@@ -468,12 +470,31 @@ class CitizenGenerator:
)
prompts.append((self._directive(PERSONA_SYSTEM_PROMPT), f"{world_ctx}\n\n{citizen_ctx}"))
responses = await self.llm.generate_batch(prompts, json_mode=True, max_tokens=800)
# A full profile contains a backstory, goals, beliefs, and
# resources. 800 tokens is too small for Chinese output and can
# truncate the JSON before the closing braces, causing avoidable
# parse failures.
responses = await self.llm.generate_batch(
prompts,
json_mode=True,
max_tokens=1400,
purpose="persona_fast",
)
# Sub-purpose differentiates persona_fast vs persona_enrich so
# timing stats aren't mixed together.
_persona_purpose = "persona_fast"
# Sub-purpose: differentiate "persona_fast" from "persona_enrich"
# so timing stats don't get bucketed together with the slow
# variants.
# (this is the persona_fast path — the enrich path is below)
# Sub-purpose: differentiate "persona fast" from "persona enrich" so
# timing stats don't get bucketed together with the slow variants.
# Callers above set this; default to persona_fast if unset.
for i, response in enumerate(responses):
idx = batch_start + i
member = cast[idx]
data = parse_json(response)
data = parse_json(response, purpose=f"居民画像生成(批次索引={batch_start + i})")
personality_data = data.get("personality", {})
personality = Personality(
@@ -536,7 +557,7 @@ class CitizenGenerator:
max_tokens=3000,
)
data = parse_json(response)
data = parse_json(response, purpose="居民关系生成")
if isinstance(data, dict) and "relationships" in data:
rel_list = data["relationships"]
elif isinstance(data, list):
@@ -624,7 +645,7 @@ class CitizenGenerator:
for i, response in enumerate(responses):
agent = batch[i]
data = parse_json(response)
data = parse_json(response, purpose=f"居民人生经历生成(居民={agent.name},ID={agent.id})")
agent.life_state = self._parse_life_history(data, agent)
def _parse_life_history(self, data: dict, agent: AgentPersona) -> LifeState: