Files
mirosociety/docs/问题记录/问题记录-硬编码与千人模拟.md
T

138 lines
5.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 问题记录:硬编码与"千人社会"模拟的业务一致性
> 记录日期:2026-09-27
> 记录人:项目相关方
> 状态:仅记录问题,不附解决方案
---
## 背景
我想搞清楚一个问题:在当前的项目实现里,如果用户设置 1000 个市民,整个模拟引擎到底是怎么跑的?我担心两个事情:
1. 供应商的 TPS 顶不顶得住;
2. 这个所谓的"千人社会模拟",在业务层面到底是不是真的有意义。
下面是我一边看代码一边问出来的问题。
---
## 一、先确认运行机制
我看到的是这样一条路径:
- 模拟每一天被分成三轮(morning / afternoon / evening),全年 365 天,共 1095 轮。
- 每一轮里,并不是所有人都去问 LLM,而是有一个 `_select_active_agents` 的机制去抽样。
- 抽样的基准是 `active_agents_per_round_min = 3`、`active_agents_per_round_max = 10`,带情绪、事件、派系、沉睡等权重加成。
- 然后这一批被抽中的人,去并发地打 LLM,做决策。
- 之后再走反应、研究、叙事等若干个 LLM 调用。
看到这里的时候,我的第一个问题浮出来了。
---
## 二、问题
### 问题 1:抽样率随人口规模塌缩
1000 人的社区,每一轮只有 3–10 个人被问,这是什么比例?
- 100 人社区:3%–10%(看起来是合理的"采样")
- 1000 人社区:0.3%–1%
- 10000 人社区:0.03%
我卖的产品叫"千人社会模拟"。可是跑出来,每一轮只有不到 10 个人在表态,那剩下 990 多个人在干什么?我看到的"社会"是从 3–10 个人里推出来的。
### 问题 2:业务参数和成本参数混在同一个常量里
那 3 和 10 到底是产品决策还是工程决策?
- 如果是产品决策 —— 它凭什么不随人口规模变?
- 如果是工程决策 —— 它为什么硬编码在 `TimeConfig` 这样一个看起来是"世界设定"的数据类里?
我觉得这两个里总有一个不对劲。
### 问题 3:上限被钳到 60%,但实际上根本用不到
我注意到有这么一行:
```python
max_active = max(base_count, int(len(agents) * 0.6))
target = min(base_count + (3 if event_occurred else 0), max_active)
```
看起来是给 1000 人准备的"60% 上限",但由于 `base_count` 只有 3–10,那个 0.6 的上限永远摸不到。也就是说这个上限是一个"为更大规模准备的逻辑",但在没有人为改 3–10 之前,它永远是死代码。
那它在暗示什么?是不是原来设想里,这个 3–10 本来就应该随规模动起来?
### 问题 4:3–10 个人的表态,能代表什么?
我再往深想一层。民意、情绪、派系分布、冲突演化 —— 整个产品叙事都建立在"涌现"两个字上。
那 3–10 个人做出的决策,能涌现出什么?尤其是当 active 抽样里还包含了"沉默偏向"(默认 w=0.3,平静的人权重天然偏低),抽样结果会进一步偏向情绪激动的小群体。
所以这到底是"千人社会的涌现",还是"6 个最激动的人的噪声"?
### 问题 5:业务承诺和实际行为之间,是不是有错配?
用户为"千人社会模拟"付费。系统在生成阶段认真生成了 1000 个市民的 backstory、性格、记忆、关系。一轮跑下来,认真读了 prompt 的只有 6 个人。
这种错配在业务上叫什么?我不太确定。但我直觉它不只是"性能问题"。
### 问题 6:除了 active 数,还有哪些是硬编码的"看似业务参数"?
我在 `world.py` 里看到了:
```python
total_days: int = 365
rounds_per_day: int = 3
active_agents_per_round_min: int = 3
active_agents_per_round_max: int = 10
```
这四个数字看起来都是默认值。但 `total_days` 和 `rounds_per_day` 是用户故事的核心节奏参数 —— 用户应该在产品层能感知到它们。
而 `active_agents_per_round_min/max` 这个东西,用户感知不到,因为它从来没出现在 UI 里。但它却对"千人模拟"的语义有决定性影响。
这到底是一个用户应该配置的旋钮,还是一个工程内部值?我越看越觉得,它**应该**是前者。
---
## 三、我没有结论,只有问题
到目前为止我没找到一份文档说明:
1. 为什么 `active_agents_per_round_min/max` 默认是 3–10;
2. 这个区间在不同人口规模下是怎么被论证过的;
3. 它是被当作"LLM 成本阀门"还是"模拟质量参数"设计的;
4. 是否有人为"千人社会"做过代表性 / 抽样率的论证。
如果不回答这四个问题,"千人社会模拟"四个字,目前在产品上只是启动时填了一个数字而已。
---
## 四、补充记录:跟 TPS 那条线是分开的两件事
我一开始问的是 TPS(供应商限流),得到的答案是:
- semaphore 默认并发 10;
- 每轮实际只打 3–10 次决策 LLM,加若干反应 / 研究 / 叙事;
- 1000 人下瞬时并发不会超过 semaphore 上限。
这条线结论清楚:当前实现下,**TPS 没问题**。
但顺着这个问题我又往下看了一眼抽样代码,发现了上面那些"业务没意义"的疑问。所以这两件事其实是分开的两件事:
- TPS:**没风险**(这是工程结论)
- 千人模拟是否成立:**存疑**(这是产品结论)
我把它们分开记在这里,免得有人把"TPS 没问题"误读成"千人模拟没问题"。
---
## 五、相关代码位置
- 抽样逻辑:`backend/app/services/engine.py` `_select_active_agents`(约 765–833 行)
- 配置常量:`backend/app/models/world.py` `TimeConfig`(约 14–17 行)
- 决策并发:`backend/app/services/llm.py` `LLMClient.generate_batch`
- 轮循环入口:`backend/app/services/engine.py` `run` 主循环(`while round_num < total_rounds`)