假设生成器 本节摘要:一个问两次同一问题的研究代理在浪费 token。诀窍是强制每份草稿落到新地方。规划器问模型一次得一个假设,这对走通的例子够了;对研究循环是错的形状——循环要带深度的排好序队列,使第一个假设失败时运行器无需另付一次完整采样就有下一个备好。两个想法组合产出那个队列:温度爬坡(temperature ramping,每过采样器提一档温度,让后续草稿漂得更远)与新颖度过滤(novelty filtering,每份草稿后测其与每个先前幸存者的嵌入距离,拒绝簇内的)。本节发一个返回脚本化 token 序列的 mock 语言模型,够走通全路径:种子提示入、温度爬坡施加、候选解析、新颖度过滤跑、排好序队列出。全程确定性——同种子永远产同队列。
本节摘要:一个问两次同一问题的研究代理在浪费 token。诀窍是强制每份草稿落到新地方。规划器问模型一次得一个假设,这对走通的例子够了;对研究循环是错的形状——循环要带深度的排好序队列,使第一个假设失败时运行器无需另付一次完整采样就有下一个备好。两个想法组合产出那个队列:温度爬坡(temperature ramping,每过采样器提一档温度,让后续草稿漂得更远)与新颖度过滤(novelty filtering,每份草稿后测其与每个先前幸存者的嵌入距离,拒绝簇内的)。本节发一个返回脚本化 token 序列的 mock 语言模型,够走通全路径:种子提示入、温度爬坡施加、候选解析、新颖度过滤跑、排好序队列出。全程确定性——同种子永远产同队列。
对应原课程:Phase 19 · Lesson 50 ·
hypothesis-generator(原英文phases/19-capstone-projects/50-hypothesis-generator/docs/en.md)。本节属「AI Scientist」赛道第一节。
阅读完本节,你应当能够:
Hypothesis id : int (运行内单调) text : str (主张) variables : list[str] (条件间变化的是什么) metric : str (运行器将测什么) baseline_ref : str | None (对比引哪篇论文或哪次运行) draft_pass : int (哪个采样趟产出此假设) temperature : float (起草时采样器设置) novelty_score : float (与先前幸存者的距离, 0..1) rank_score : float (排序用的加权和)
variables 与 metric 不是自由文本,解析器从带标签响应里拉它们。第 50 节的运行器在构建实验配置时直接读这些字段。baseline_ref 可选但推荐,第 51 节的评估器需基线对比,假设省了它,评估器回退到同指标的上次运行。
每个框有硬契约,这是循环直白之处也是有趣之处。
从 t_min 起、t_max 止、步长 (t_max - t_min)/(n_passes - 1)。每趟在当前温度调采样器。默认调度六趟从 0.2 到 1.2——六趟够填队列,不付会被新颖度过滤拒掉的样本。低于 0.2 模型鹦鹉学舌种子;高于 1.2 响应跑题、解析失败。
每份草稿解析后,生成器嵌入文本并与每个已接受假设比。嵌入是词 token 的小哈希袋,归一到单位长。两单位向量余弦距离是 1 - dot(a, b)。草稿通过与任何先前幸存者的最小距离超 novelty_threshold(默认 0.25)则过。哈希嵌入不花哨——确定性、零依赖、够抓「两份草稿共享大部分名词」的明显情况;生产换小句模型,接口不变。
rank_score = w_novelty * novelty_score + w_specificity * specificity_score + w_testability * testability_score
三子分:novelty_score 是与先前幸存者的最小嵌入距离;specificity_score 是假设中具体变量数除目标数;testability_score 是假设同时指定指标与基线则一、只有指标则半、否则零。默认权重 0.4/0.3/0.3,活在生成器配置里使下游课可调。
code/main.py 定义 Hypothesis、MockLLM、HypothesisGenerator 与确定性 demo。生成器暴露单一 run(seed_prompt) 返排序队列;趟数从 GeneratorConfig.n_passes 读而非参数传。嵌入是 token 哈希袋,新颖度过滤一函数,排序分一函数,无 numpy 依赖,纯 stdlib 保可移植。
@dataclass class Hypothesis: id: int; text: str; variables: list; metric: str baseline_ref: str | None; draft_pass: int; temperature: float novelty_score: float; rank_score: float class HypothesisGenerator: def run(self, seed_prompt) -> list[Hypothesis]: survivors = []; hid = 0 temps = self.cfg.schedule() # 0.2..1.2 六个值 for p, t in enumerate(temps): raw = self.llm.sample(seed_prompt, t, self.seed) parsed = parse_tagged(raw) # 拉 text/variables/metric/baseline if parsed is None: continue # 解析失败路径 nov = min_embedding_distance(parsed.text, survivors) if nov < self.cfg.novelty_threshold: continue # 重复丢弃 spec = len(parsed.variables) / self.cfg.target_vars test = 1.0 if (parsed.metric and parsed.baseline_ref) \ else (0.5 if parsed.metric else 0.0) rank = 0.4*nov + 0.3*spec + 0.3*test survivors.append(Hypothesis(hid, **asdict(parsed), draft_pass=p, temperature=t, novelty_score=nov, rank_score=rank)) hid += 1 return sorted(survivors, key=lambda h: -h.rank_score)
MockLLM.sample(prompt, temperature, seed) 在给定 (prompt, temperature, seed) 三元组下确定,保持按 (prompt_signature, temperature_bucket) 键的脚本响应表;无条目返解析失败的 fallback,测试覆盖此路径。种子混入响应使同 (prompt, temperature) 不同种子产不同草稿;测试钉种子保复现。
设计要点:输出是按
rank_score降序的假设列表——第 50 节运行器弹头、跑实验、第 51 节评估器写判定;判定说假设错,运行器弹下一个。队列有限,空时编排器可拓宽种子提示重跑生成器或停并报预算耗尽。每个框的硬契约使四课组合成无人研究循环,人可在任一边界介入。
这是 AI Scientist 风格自主研究代理的「头脑」阶段。真实系统(AI-Scientist v1/v2、Sakana、斯坦福 STORM)用真 LLM(GPT-4/Claude)替 mock,嵌入换 sentence-transformers 或 API embedding,新颖度过滤可加语义相似度。但底层契约同:温度爬坡扩多样性、嵌入距离去重、三子分排序。LangGraph/CrewAI 把这包成图节点,但生成-过滤-排序的逻辑仍需手写。本节 mock 的价值是确定性测试——真 LLM 随机,无法断言队列形状,先在 mock 上把循环跑通再换真模型。
code/main.py + code/tests/test_generator.py(覆盖线性路径、重复拒路径、解析失败路径、温度边界、排序)。HypothesisGenerator.run 是编排器入口,产队列供第 50/51 节消费。GeneratorConfig 的权重与阈值是可调旋钮,下游课可调。换真 LLM 只需 MockLLM 替成真 API 适配器,接口 sample(prompt, temperature, seed) -> str 不变。
all-MiniLM-L6-v2),对比新颖度过滤的准确率。MockLLM 换成 OpenAI/Anthropic API,跑同种子提示,人工审队列质量。w_novelty/w_specificity/w_testability,观察队列排序变化,讨论哪个子分最该重。n_passes 从 3 到 12,观察队列深度与新颖度的权衡。baseline_ref,确认评估器回退到同指标上次运行。下一节,我们做「文献检索」——BM25 词法检索 + 引用图遍历,在运行器起沙盒前确认假设是否已被证明。