fix simulation reporting and research resilience

This commit is contained in:
2026-09-28 15:43:23 +08:00
parent 23297be2d7
commit a428214c53
25 changed files with 1488 additions and 244 deletions
@@ -0,0 +1,109 @@
# 问题记录:2026-09-27
> 状态:待处理(明天再动)
> 记录人:项目相关方
> 本文件只记问题,不附解决方案。
---
## 一、背景
今天在排查项目时发现两个独立但都和"千人模拟"业务相关的问题。先记下来,明天再决定怎么处理。
---
## 二、问题一:每轮 active agent 数量硬编码,与"千人模拟"业务叙事冲突
### 现象
在 [`backend/app/services/engine.py` 的 `_select_active_agents`](../backend/app/services/engine.py) 里,每一轮参与决策的市民数量由这两个常量决定:
```python
active_agents_per_round_min: int = 3
active_agents_per_round_max: int = 10
```
来源:[`backend/app/models/world.py` 的 `TimeConfig`](../backend/app/models/world.py)
### 我观察到的问题
1. **抽样率随人口规模塌缩。** 100 人社区 → 3%–10% 采样,1000 人 → 0.3%–1%,10000 人 → 0.03%。但产品的对外叙事是"千人社会模拟"。
2. **业务参数 vs 工程参数混淆。** 这两个数字看起来是 `TimeConfig`(业务/世界设定),但更像是 LLM 成本阀门。混在一起,没办法独立调。
3. **`len(agents) * 0.6` 这个上限看起来是为更大规模预留的,但在默认 3–10 的基础上永远是死代码。** 暗示当初设计是想过随规模调整的。
4. **3–10 个人的表态,能涌现出什么?** 默认权重 w=0.3,平静的人天然偏低,抽样会偏向情绪激动的小群体。
5. **业务承诺 vs 实际行为错配。** 用户为"千人社会模拟"付费,生成阶段认真做了 1000 个 backstory;一轮跑下来认真读 prompt 的只有 6 个。这到底是性能问题还是产品问题?
### 待澄清的几个点
- 为什么 `min/max` 默认是 3–10?
- 是否有人为"千人"规模做过代表性 / 抽样率的论证?
- 这个参数应该让用户在产品层感知到(作为旋钮),还是只放在工程内部?
---
## 三、问题二:搜索后端从 DuckDuckGo 替换为 SearXNG
### 现象
现在 [`backend/app/services/research.py`](../backend/app/services/research.py) 用的是 `duckduckgo_search`:
```python
from duckduckgo_search import DDGS
with DDGS() as ddgs:
return list(ddgs.text(query, max_results=max_results))
```
我想替换为自托管的 SearXNG,原因(这里只描述动机,不给方案):
- DDG 经常触发反爬 / CAPTCHA,结果不稳定
- 模拟场景需要可重复性(同一 query 多次结果尽量一致)
- 想避免外部限流风险
### 相关材料(已确认)
项目里已有 SearXNG 的 skill 文档:[`skills/searxng_skills/`](../skills/searxng_skills/)
关键信息(待修改时要严格遵守):
- **域名是 `searxng.erhe.top`**(不是 `ehre.top` — `h`/`r` 反着,#1 常见坑)⚠
- **必须带 `token=fGWEFWGrgasegtjt1jUJG`**,否则 403
- 当前服务器 GET 即可(本地 stock docker 默认 POST,**不要照搬本地配置**)
- JSON 200 路径:`/search?format=json&token=...`
- 返回字段:`title / content / url`,需要重命名映射到现有 `title / body / href`
### 已处理
- ✅ 域名拼写错误问题,今天已修。
### 待处理(明天动手)
- [ ] `research.py` 的 `_raw_search` 替换为 SearXNG 调用
- [ ] `__init__` 加 `searxng_base_url / searxng_token / searxng_timeout` 参数
- [ ] `config.py` 加对应 `Settings` 字段
- [ ] `main.py` 注入新参数
- [ ] `.env` 写 `SEARXNG_BASE_URL` / `SEARXNG_TOKEN` / `SEARXNG_TIMEOUT`
- [ ] 是否保留 DDG fallback?待定
- [ ] 是否加 query 短缓存(避免同 query 短时间重复打后端)?待定
### 还需要决定的
- `searxng_timeout` 取多少?skills 里两处不一致(`SKILL.md` 是 30,`searxng_skills.md` 是 15)。模拟里 30 太久(卡轮),5 太短。倾向 15。
- `safesearch` 取 0 还是 1?模拟里有些社会议题可能需要未过滤内容。
---
## 四、相关代码位置
- 抽样逻辑:`backend/app/services/engine.py` `_select_active_agents`(约 765–833 行)
- 抽样配置:`backend/app/models/world.py` `TimeConfig`(约 14–17 行)
- 搜索实现:`backend/app/services/research.py`
- 搜索配置:`backend/app/config.py` `Settings.search_enabled / max_searches_per_round`
- LLM 客户端 / semaphore:`backend/app/services/llm.py`
- LLM 并发配置:`backend/app/config.py` `Settings.max_concurrent_llm_calls`
- SearXNG skill 文档:`skills/searxng_skills/SKILL.md`
---
## 五、与上一份记录的关系
这是第二份问题记录(第一份是 [问题记录-硬编码与千人模拟.md](./问题记录-硬编码与千人模拟.md))。