Codebase (2026-06-27): this engine now lives in meno-gaokao-counsellor (core/…), extracted from reflection-game — see gaokao-standalone-plan. Module paths below use the new core/ layout.

当前实现(2026-06-24)— investigator 已并入 unified_turn

独立的 investigate() 函数已是 legacy、当前流程不再调用。investigator 的角色(top vs contender + 针对此人的问题 + 来源)现在是 unified_turn 这一次融合调用的一部分;输出含 question / prose / top_take / contender_take / elicit / scene_gist / sources(did_you_know 已退役)。新增 elicit = 一句最想听学生自己说的追问(UI 渲染成 文本 + 麦克风)。来源纪律 _auth_sources() 现也接受「雨来-verified」层级。模型:unified_turn 用 deepseek-chat,画像/报告用 deepseek-v4-pro

这是什么 · What this is

中文:每一站的调查员——它读什么、问什么、怎么落地。世界推演引擎(WTE)选定维度后,调查员读人类模型(画像 + 不确定性地图)为这个学生定出一个具体问题,再用 grounding(本地KB + 雨来)加实时联网检索去调查它。下面是当前提示词、机制、与输出处理。代码:core/ranked.py::investigate()

EN: The per-stage investigator — what it reads, what it asks, how it grounds. After the WTE picks a dimension, the investigator reads the human model (profile + uncertainty map) to define a person-specific question, then investigates it using grounding (local KB + 雨来) plus live web search. Below: the current prompt, mechanism, and output handling. Code: core/ranked.py::investigate().

调查员提示词(当前)/ The investigator prompt (current)

中文。 WTE 选定维度后,调查员先读画像,为这位具体学生在该维度上定出最该弄清的一个问题,再围绕它调查(同一维度对不同人是不同问题)。逐字提示词(注册表 key investigate):

你是"反思均衡"志愿推演里的调查员(investigator)。原则:帮学生想清楚,绝不替他下结论、不说哪个更好。
本站要考量的维度(由世界推演引擎 WTE 选定):【{factor_label}】—— {factor_ask}
学生当前排第1的选择:{top}
该选择的真实背景:
{g_top}
 
学生的其他候选(择一作为"对比项"——挑在【{factor_label}】这个维度上与第1名*差异最明显*的那个):
{g_rest}
 
这位学生的画像(务必先读懂——你要据此决定"对这个人、在这个维度上,最该弄清的那一个具体问题"):
{profile_text}
 
任务:① 先读画像,在【{factor_label}】这个维度里,为*这位具体学生*定出最有信息价值、最戳中他纠结或盲区的**一个具体问题**(因人而异,不要泛泛而谈);② 再围绕这个问题做调查。
 
{src_rule}
产出一个 JSON 对象(简体中文),不要多余文字:
{{
 "question": "你为这个人定的那个具体问题(一句话,因人而异)",
 "prose": "一段110字内的、具体写实的'第一人称代入'小场景:学生正过着【{top}】这条路,在这个问题上真切撞见的日常(用真实课程/岗位/细节,可戏剧化不可捏造)",
 "contender": "你选作对比项的候选名(从上面候选里选一个)",
 "top_take": "一句:{top} 在这个问题上的真实情况(尽量带上检索到的权威数字/事实)",
 "contender_take": "一句:对比项在同一问题上有何不同(往往是学生没意识到的)",
 "sources": ["你实际采信的权威来源名(2-3个,如'XX大学2023就业质量报告''麦可思2023');没检索就留空[]"]
}}

English. After the WTE picks a dimension, the investigator first reads the profile to define the single most-worth-clarifying question for this student on that dimension, then investigates it (same dimension → different question per person). Verbatim prompt (registry key investigate).

JSON output fields: question (因人而异的问题) · prose (第一人称小场景) · top_take/contender_take (第1名 vs 对比项) · sources (权威来源). {src_rule} is the authoritative-source-only rule injected when web search is on — see 机制 / 输出处理 below.

机制 — OpenRouter 网络插件(与模型无关)· Mechanism — OpenRouter web plugin (model-agnostic)

中文:没有单独的「搜索步骤」。我们把调查员那一次正常的 chat-completion 发给 OpenRouter,并挂上网络插件;OpenRouter 根据提示词跑一次检索,把结果注入模型上下文,模型(DeepSeek-V3)在同一次调用里就着这些结果推理。

EN: There is no separate “search step.” We send the investigator’s normal chat-completion to OpenRouter with the web plugin attached; OpenRouter runs a search derived from the prompt, injects the results into the model’s context, and the model (DeepSeek-V3) reasons over them in the same call.

# core/ranked.py :: _llm_json(..., online=True)
payload = {"model": "deepseek/deepseek-chat",
           "messages": [{"role": "user", "content": prompt}],
           "response_format": {"type": "json_object"},
           "max_tokens": 1800, "temperature": 0.6}
if online:
    payload["plugins"] = [{"id": "web", "max_results": 4}]   # <-- the web search

中文

  • 模型deepseek/deepseek-chat(V3)。插件与模型无关——V3 仍是我们的推理器;换 Qwen 等也是同一插件、没有优势(若哪天想要原生带引用的检索,备选是 Perplexity Sonar)。
  • 开关GAOKAO_WEBSEARCH 环境变量(默认开)。关掉 → 退回一次普通的 grounded 调用。
  • 范围:只有调查那一次调用联网(它能容忍延迟、且对准确性要求最高);问卷是精选的、结尾是确定性的——两者都不检索。

EN:

  • Model: deepseek/deepseek-chat (V3). The plugin is model-agnostic — V3 stays our reasoner; Qwen/others would use the same plugin with no edge (Perplexity Sonar = the alternative if natively-cited search is ever wanted).
  • Toggle: GAOKAO_WEBSEARCH env var (default ON). Off → a plain grounded call.
  • Scope: only the investigation call runs online (the latency-tolerant, accuracy-critical one); the quiz is curated and the end is deterministic — neither searches.

检索/提示词的输入 · Inputs to the search/prompt

中文investigate(factor, options, profile_text) 组装:

  • 当前因素(如 课程与学业 / 职业前景),
  • grounding_block(top)——第 1 名选项在 859 专业知识库里的「事实/观点」卡片,
  • grounding_block(其余每个选项)——用于挑对比项,
  • 玩家画像PersonalityFile.serialize_for_prompt()),
  • SRC_RULE——来源纪律指令(见下),仅在联网时注入。

EN: investigate(factor, options, profile_text) assembles:

  • the current factor (e.g. 课程与学业 / 职业前景),
  • grounding_block(top) — the #1 option’s fact/opinion card from the 859-major KB,
  • grounding_block(each other option) — for picking the contrast,
  • the player profile (PersonalityFile.serialize_for_prompt()),
  • the SRC_RULE — the source-discipline instruction (below), injected only when online.

SRC_RULE(逐字 · verbatim):

**用网络检索核实这一步的关键事实**(只针对【{factor.label}】这一维度、针对 {top} 与你选定的对比项这两者)。
**只采信权威来源**:高校毕业生就业质量报告、麦可思(MyCOS)就业蓝皮书、阳光高考平台、教育部/国家统计局官方数据、该校官网;
**忽略营销软文、随机新闻、贴吧/营销号**。
**铁律**:① 任何具体数字必须来自*明确覆盖'该校+该专业'*的权威来源;检索结果若不含该校该专业的具体数据,就**用定性描述、不要编数字**(如'对口率较高/一般')。
② **绝不**把一个机构/地区的数据安到另一个机构头上(例如拿香港高校网站的数据去描述内地高校)。
③ **不要在正文里插入任何引用标记/链接**,来源只写进 sources 字段,且 sources 里只列真正支持你所述事实的那个来源。
检索不到可靠来源时退回到上面的 grounding。

输出 — 接口 + 后处理 · Output — interface + post-processing

中文:模型返回一个 JSON 对象:{prose, contender, top_take, contender_take, did_you_know, sources}(注:did_you_know 已于 2026-06-17 退役,不再生成;后处理逻辑对其余字段仍适用)。然后 investigate() 对它后处理(准确性是在这里被强制的,而不只是被请求):

  1. 对每个展示字段做 _clean()prose / top_take / contender_take / did_you_know):剥掉行内引用——[text](url) 与裸的 [域名] 都剥——以及引用被删后残留的「根据,」连接词。引用永不出现在正文里,只活在 sources 字段。
  2. sources_auth_sources():一个确定性过滤器,保留权威名称(域名/关键词:edu.cngov.cn、麦可思、就业质量、阳光高考、chsi、报告、蓝皮书…),丢弃新闻/UGC/营销(sina, sohu, 163, qq, baidu, zhihu, tieba, toutiao, ifeng, news., weibo, 小红书, bilibili)。上限 3 条。
  3. 清洗后的 sources 在每一站下方渲染为 来源: …

EN: The model returns one JSON object: {prose, contender, top_take, contender_take, did_you_know, sources}. Then investigate() post-processes it (this is where accuracy is enforced, not just requested):

  1. _clean() on every displayed field (prose / top_take / contender_take / did_you_know): strips inline citations — both [text](url) and bare [域名] — and orphaned “根据,” connectors left when a citation is removed. Citations never appear in the prose; they live only in sources.
  2. _auth_sources() on sources: a deterministic filter that keeps authoritative names (domains/keywords: edu.cn, gov.cn, 麦可思, 就业质量, 阳光高考, chsi, 报告, 蓝皮书…) and drops news/UGC/marketing (sina, sohu, 163, qq, baidu, zhihu, tieba, toutiao, ifeng, news., weibo, 小红书, bilibili). Capped at 3.
  3. The cleaned sources render under each stage as 来源: ….

为什么后处理重要(它修过的一个真实 bug)· Why the post-processing matters (a real bug it fixed)

中文:模型曾把一个数字安到不匹配的来源上(把北大的事实引到一个香港招生网站),还把引用写进了正文。提示词规则能减少这种事;而 _clean + _auth_sources 过滤器保证展示文本不含行内引用、所示来源只有权威。若检索不到任何权威信息,模型被要求保持定性、退回 grounding 知识库——绝不编数字。

EN: The model once attached a number to a mismatched source (a PKU fact cited to an HK admissions site) and inlined the citation. The prompt rules reduce this; the _clean + _auth_sources filters guarantee the displayed text carries no inline cite and the shown sources are authoritative-only. If nothing authoritative is found, the model is told to stay qualitative and fall back to the grounding KB — never to invent a number.

延迟 · Latency

中文:约 17 秒/次(检索在 ~20 秒基线上几乎不加时)。被全量预取(prefetch-all)藏掉:在用户读当前一站时,后台已把下一站对所有候选都预生成,所以重排几乎是瞬时的。

EN: ~17s/call (search adds little over the ~20s base). Hidden by prefetch-all: while you read the current stop, the next stop is pre-generated for every option in the background, so re-ranking is near-instant.