1. 这篇博客真正要解决的问题:为什么我们需要“自主数学发现”
先问一个问题:你见过几个多智能体项目,是真的让智能体自己“发现”了新东西,而不是把已有的知识搬运来搬运去?
如果你用过 LangGraph、AutoGen、CrewAI 这一类的多智能体框架,我想你一定经历过这种场景:几个 Agent 来回对话,一个负责提出方案,一个负责评审,最后看起来讨论得很热闹,但输出的结果要么是基于已有知识的组合,要么是网上能找到的标准答案。整个流程像是“多智能体扮演”,而不是“多智能体发现”。
为什么会这样?
因为大多数多智能体框架设计出来是为了解决“任务编排”,而不是为了解决“未知知识的自主探索”。智能体被组织成流水线,每个人的角色是固定的,交互的模式是预设的,目标函数是已知的。整个系统在做的是“搜索”而不是“发现”——它知道正确答案长什么样,只是在努力的路径中找到一条。
但数学发现这件事不一样。
数学发现没有一个明确的答案模板,也没有一个可以提前写进 Prompt 的“正确结果”。它需要智能体在开放环境中不断尝试、构造、验证、推翻、再构造。这个过程的本质不是“在已知空间中检索”,而是在巨大甚至无限的符号空间里进行探索和筛选。你没法用数据库、知识库、检索增强生成这类技术去做这件事,因为你要找的东西根本不在库里。
这就引出了这篇博客的主题:开放世界多智能体环境中的自主数学发现。
我先把核心判断放在前面:多智能体系统最适合切入的科研方向,不是“让多个 Agent 协作写文档”,而是“让多个 Agent 协作构造数学猜想和证明策略”。因为数学符号空间天然适合程序化表达,数学验证天然可以形式化,数学发现的每一步都可以被自动评估和打分。这三个特征,恰好是多智能体系统最需要的“可反馈、可验证、可迭代”闭环。
读完这篇文章,你会理解四件事:
第一,什么是“自主数学发现”,它是如何从早期数学定理证明器一路演化到今天的多智能体框架的。
第二,为什么“开放世界多智能体环境”特别适合做数学发现,这种环境的本质是什么。
第三,如何用 Python 从零搭建一个“正反博弈 + 裁判”的多智能体数学发现系统,包括完整的代码实现、运行结果和验证方法。
第四,在真实科研或工程项目中,这种系统的边界在哪里,哪些问题它解决不了,哪些东西我们应该尽早避免。
整个过程不需要你有数学专业背景,只需要你写过 Python,知道多智能体框架的基本概念。我会尽量把每一个抽象概念落到能跑通的代码上。
2. 基础概念:什么是自主数学发现,它和定理证明有什么区别
在展开代码之前,我们需要先把概念理清楚。很多读者一听“数学发现”,第一反应是“这不就是自动定理证明吗?”其实两者有本质差异。
自动定理证明(ATP,Automated Theorem Proving)的目标是:给定一个猜想,证明它是真的或假的。证明过程通常用逻辑规则、公理系统、搜索算法来完成。它的核心是“验证”。
自动数学发现(AMD,Automated Mathematical Discovery)的目标是:在没有任何预设猜想的前提下,让系统自己提出有趣的猜想、构造反例、发现新结构。它的核心是“猜想生成 + 验证 + 修正”。
更直白一点:
- 定理证明器是“给你一道题,让你证明对错”。
- 数学发现系统是“连题目都自己出,还要想办法解答”。
举一个例子。如果人类数学家在研究“图论中的色数问题”,定理证明器会处理“每个平面图是否都能用四种颜色着色”这种具体命题。而一个自主发现系统要做的是:先定义什么是图,定义什么是一个“有趣的图论性质”,然后让多个智能体在图的符号空间中自由探索,自己发现“四色猜想”这个值得研究的命题——它可能能发现,也可能发现的是完全不同的东西。
从这个角度看,数学发现系统面临的核心挑战有三个:
- 探索空间巨大。以“群论”为例,仅仅考虑有限群的构造,符号空间就已经大得惊人。用穷举是不可能的。
- 价值判断难定义。即使系统生成了一万个新命题,怎么判断哪一个“有趣”“重要”?在证明完成之前,很多命题连“真假”都不知道。
- 验证成本高。生成一个猜想容易,验证一个猜想往往需要指数级的计算量。
多智能体系统之所以在这个问题上展现出潜力,是因为它能把这三个挑战拆成不同角色的职责,形成自动化的循环。
这里我给出一个通俗类比:
想象一个数学研究所,里面有研究员、怀疑者和评审委员会。研究员每天提出新想法,怀疑者专门找想法的漏洞,评审委员会判断这个想法是否值得进入下一轮。数学研究所里真正推动知识前进的,不是单个人的努力,而是这种“提出—批判—评审—再提出”的制度化协作。
多智能体数学发现系统,本质上就是把数学研究所的运转机制搬进代码里。
为了让这个机制有效运作,我们就需要一个“开放世界环境”。这个词听起来玄乎,但本质上就两条:第一,系统不能只在一个封闭的、已经预设好答案的题库里跑;第二,智能体必须能自由地生成、验证和丢弃候选对象。开放世界的意义不在于地图有多大,而在于智能体能不能自己定义要探索的目标。
下一节,我会解释开放世界多智能体环境的三个核心角色,以及为什么“正反博弈 + 裁判”这种架构特别适合数学发现的早期探索。
3. 开放世界多智能体环境的核心设计:正反博弈与裁判机制
在深入代码之前,我先拆解一个关键问题:多个智能体到底怎么协作,才能产生真正的发现,而不是空转?
从实际经验来看,最简单的“协作”是派一个智能体做加法、一个做减法,那最后只是把答案拼接起来,没有深度。要让协作产生认知增量,需要设计强制的“认知冲突”。
我把这套架构称为正反博弈 + 裁判机制。它由三个角色组成:
3.1 正向方:猜想生成器
正向方的职责是不断提出新的数学对象或猜想。它接收当前的知识库和探索历史,然后生成新的候选命题。它的目标函数是“新颖性”——希望提出尽可能偏离已有知识的想法。
但注意,正向方不能完全随机地生成。它需要有某种“搜索策略”。比如,它可以基于现有群论中的特定结构,做“变异”操作,生成新的群结构;也可以用类比推理,把某个定理的形式从一个领域平移到另一个领域。
3.2 反向方:攻击者
反向方的职责是尽可能摧毁正向方提出的猜想。它不是一个简单的“否定角色”,而是一个具有验证能力的批判者。
反向方会对每条猜想做三件事:
- 检查是否符合基本定义和语法。
- 尝试构造反例。
- 在大规模随机采样上进行验证。
反向方的存在至关重要。如果没有它,正向方会在海量无效猜想中空转。数学史上几乎所有有意义的猜想都经历过“被攻击—被修正—再攻击—再修正”的过程。这个角色让系统有了自我纠错能力。
3.3 裁判方:价值判断器
当正向方提出猜想,反向方没有找到反例时,裁判方要做出最终判断:这条猜想是否值得保留?
裁判方不负责证明真假,只负责判断“一个经过攻击仍然成立的新命题,是否配得上加入知识库”。它主要看三个维度:
- 新颖性:这个猜想是否和已有知识冲突或显著不同。
- 可验证性:这个猜想能否在可接受的计算时间内被进一步检验。
- 价值:这个猜想是否指向更深层的数学结构,或者能反过来启发新的构造。
裁判的决策会直接影响下一步的搜索方向。比如,一个被判定为“高度有价值”的猜想会被写入知识库,并作为正向方下一轮生成的上下文;一个被判为“低质”的猜想会被丢弃,同时正向方会收到信号,避免生成同类内容。
3.4 为什么这个架构适合自主数学发现
这套“正反博弈 + 裁判”机制之所以适合数学发现,核心原因在于它构建了一个自我纠错的知识进化闭环。
没有反向方,系统会快速收敛到大量假命题,因为生成太容易了;没有裁判,系统会陷入无休止的生成—攻击循环,因为攻击者和生成者会互相纠缠,无法沉淀下真正有价值的信息;没有正向方,系统就谈不上“发现”,只能做验证。
这个机制带来的另一个好处是:它把“验证”的责任从系统外部挪到了系统内部。传统数学研究中,哪怕生成了猜想,验证也要人来做。而在这种多智能体架构里,验证被拆成了一个持续运行的子系统,不断对已有知识进行压力测试。这就让系统能够在一个开放世界里长期运行,而不是做一次搜索就结束。
理解了这个设计,你再看多智能体数学发现的新概念就不会觉得玄了。很多所谓的新框架,本质上都是在“正反博弈 + 裁判”的基础上做变体:
- 有的换成了“生成者 + 批评者 + 打分者”,本质一样。
- 有的是多层循环,比如底层生成、中层攻击、顶层裁判,本质仍然一样。
所以在动手实现之前,我们先确定一个原则:不要被框架的花哨名词带偏,先抓住“提出—攻击—裁判”这三个核心动作。
4. 环境准备与前置条件
下面进入实操。我们会在 Python 环境里实现一个最小但完整的“正反博弈 + 裁判”多智能体数学发现系统。这个系统不会连接任何大语言模型,也不需要调用外部 API。它会使用纯 Python 和少量库来实现三个 Agent 的协作,并把数学场景限定在一个我们可控的符号空间里。
4.1 为什么不用大语言模型
你可能会问:现在做多智能体系统,一般都是接 ChatGPT 或者开源大模型,为什么不直接用?
原因有三个。
第一,可复现性。大模型的输出带有随机性,用它做数学发现,你很难判断一个猜想是“被发现了”还是“模型瞎编的”。在一个开源、固定种子的系统里,我们能清晰地看到每一步的推理逻辑。
第二,调试难易度。接大模型之后,你的调试链条会变得很长:Prompt 怎么设计、模型参数怎么调、API 怎么限流、token 怎么控制。这会把核心逻辑—多智能体的协作机制—淹没在工程噪音里。
第三,数学发现的关键不是语言能力,而是符号操作和验证能力。大模型擅长生成“看起来像数学”的句子,但不擅长证明“这个句子是否成立”。我们需要的是一个透明的、可以用代码完全验证的系统。
当然,实际工程中完全可以把它和 LLM 结合:让 LLM 负责生成更多样的候选猜想,让代码负责验证。但第一步,务必先把协作机制跑通。这也是这篇教程选择“不用大模型”的原因。
4.2 安装依赖
本教程的代码只需要 Python 3.9+ 和以下几个库:
pip install numpy networkx说明一下每个库的用途:
numpy:用于生成随机数和矩阵运算,我们会用它来实现一些简单的代数对象。networkx:用于构建和操作图结构,我们会使用图作为数学发现的载体。
另外,我们会使用 Python 标准库中的itertools和random,不需要额外安装。
4.3 代码结构
我们会创建两个文件:
mathematical_discovery.py:核心代码,包含三个 Agent 和环境定义。run_discovery.py:运行入口,负责初始化环境并启动多智能体循环。
现在开始搭建。
5. 核心流程拆解:从数学空间定义到多智能体循环
在开始写代码之前,我先把整个系统的数据流画成文字流程,这样编码时思路会更清楚。
初始化数学模型空间 ↓ 正向方(猜想生成器)生成候选命题 ↓ 反向方(攻击者)构造反例 / 验证 ↓ 正向方根据攻击反馈修正猜想 ↓ 裁判方判断是否收录进知识库 ↓ 更新知识库,开始下一轮整个循环会持续执行,直到达到最大迭代轮数或知识库规模达到预设阈值。
5.1 数学空间的设计
为了让多智能体有“数学发现”的空间,而不是在一个封闭的题库里打转,我们需要选择一个足够有探索性的数学对象。
这里选择**图论中的“图”**作为数学发现载体,原因有三个:
- 图的定义简单清晰。
- 图的性质非常多,有大量开放式问题可以探索。
- 用
networkx可以方便地生成、操作和计算图的性质。
具体来说,我们会让正向方生成一个“候选图”,并尝试提出关于它性质的猜想。反向方则尝试寻找这张图的反例或反证。裁判方最终判断这个图是否值得保留。
实际上,这种设计逻辑可以推广到群论、数论、组合数学等其他领域。只要我们换了底层的数据结构和验证函数,整个多智能体的协作骨架不需要变化。
5.2 正向方的逻辑
正向方生成候选对象的方式是:从一个“种子图”出发,进行若干次随机变换。
变换操作包括:
- 添加一条边。
- 删除一条边。
- 交换节点标签。
- 随机生成一个小规模图。
这种基于“变异”的搜索策略,远比在全部图空间随机采样更高效,因为它继承了种子图的部分良好性质,只在局部做扰动。这很像是进化算法里的“变异算子”。
5.3 反向方的逻辑
反向方拿到候选图之后,会执行以下验证:
- 检查图是否连通。
- 计算图的“发现价值分数”是否高于阈值。
- 检查该图是否和已有知识库中的图同构(如果同构,说明它已经被发现过了)。
这里我们没有实现复杂的数学证明,我们把它简化成一组可计算的验证函数。如果你把底层模型换成具体的数学结构,这些验证函数可以替换为真正的定理证明器。
5.4 裁判方的逻辑
裁判方的职责是:
- 综合正向方的“价值分数”和反向方的“攻击结果”。
- 如果攻击方没有发现致命问题,且价值分数高于动态阈值,则将候选图加入知识库。
- 如果知识库变大了,动态阈值也相应提高,防止知识库被低质量图淹没。
这就是一个非常清晰的多智能体生态:生成、攻击、裁判、更新。
6. 完整示例代码实现
下面给出两个文件的完整代码。
6.1 文件mathematical_discovery.py
# 文件路径:mathematical_discovery.py import random import networkx as nx import numpy as np from itertools import combinations # 确保结果可复现 random.seed(42) np.random.seed(42) class GraphSpace: """ 数学发现环境:定义图空间的基本操作。 """ def __init__(self, min_nodes=4, max_nodes=8, edge_prob=0.4): self.min_nodes = min_nodes self.max_nodes = max_nodes self.edge_prob = edge_prob def random_seed_graph(self): """随机生成一个种子图,作为探索的起点。""" n = random.randint(self.min_nodes, self.max_nodes) g = nx.gnp_random_graph(n, self.edge_prob, seed=random.randint(0, 10000)) # 确保至少有一个连通分量 if not nx.is_connected(g): g = nx.complete_graph(n) return g def mutate(self, graph): """ 对图进行随机变异,生成新的候选图。 变异操作包括:加边、删边、重连、随机替换。 """ g = graph.copy() n = g.number_of_nodes() action = random.choice(["add_edge", "remove_edge", "relabel", "replace"]) if action == "add_edge": possible_edges = list(combinations(g.nodes(), 2)) existing_edges = set(g.edges()) candidates = [e for e in possible_edges if e not in existing_edges] if candidates: u, v = random.choice(candidates) g.add_edge(u, v) elif action == "remove_edge": if g.number_of_edges() > 1: edge = random.choice(list(g.edges())) g.remove_edge(edge[0], edge[1]) elif action == "relabel": if n > 0: mapping = {node: (node + random.randint(0, n - 1)) % n for node in g.nodes()} g = nx.relabel_nodes(g, mapping, copy=True) else: g = self.random_seed_graph() return g class GeneratorAgent: """ 正向方:猜想生成器。 它负责在当前知识库基础上生成新的候选图。 """ def __init__(self, graph_space, mutation_rate=0.3): self.graph_space = graph_space self.mutation_rate = mutation_rate self.history = [] def generate(self, knowledge_base): """ 生成一个候选图。 - 如果知识库为空,随机生成一个种子图。 - 如果知识库不为空,从知识库中选一个图进行变异。 """ if not knowledge_base: candidate = self.graph_space.random_seed_graph() else: base = random.choice(knowledge_base) candidate = self.graph_space.mutate(base) self.history.append(candidate.copy()) return candidate class CriticAgent: """ 反向方:攻击者。 它负责验证候选图是否“存活”,并尝试攻击候选图的价值。 """ def __init__(self, min_value_threshold=0.4): self.min_value_threshold = min_value_threshold def attack(self, candidate, knowledge_base): """ 返回 (attack_result, value_score, reason) 如果 attack_result 为 False,代表候选被攻击方否决。 """ # 检查图是否连通 if not nx.is_connected(candidate): return False, 0.0, "图不连通" # 检查是否与知识库中的图同构 for existing in knowledge_base: if nx.is_isomorphic(candidate, existing): return False, 0.0, "图与知识库中已有图同构" # 计算一个“价值分数” value_score = self._evaluate(candidate) if value_score < self.min_value_threshold: return False, value_score, "价值分数过低" return True, value_score, "通过攻击" def _evaluate(self, graph): """ 价值评估函数: 这里用边数、三角形数量、连通性的组合来定义一个启发式价值。 实际项目中可以替换为更复杂的数学性质。 """ n = graph.number_of_nodes() m = graph.number_of_edges() triangles = sum(nx.triangles(graph).values()) // 3 # 归一化到 [0, 1] 区间 edge_density = m / (n * (n - 1) / 2) if n > 1 else 0 triangle_density = triangles / (n * (n - 1) * (n - 2) / 6) if n > 2 else 0 value = 0.4 * edge_density + 0.6 * triangle_density return round(value, 4) class JudgerAgent: """ 裁判方:价值判断器。 它根据攻击方的结果和候选图的新颖性,决定是否收录候选图。 """ def __init__(self, novelty_threshold=0.1): self.novelty_threshold = novelty_threshold def judge(self, candidate, attack_result, value_score, knowledge_base): """ 返回 (is_accepted, reason) """ if not attack_result: return False, "攻击方未通过" # 新颖性检查:理想情况下应该用图距离度量。 # 这里简单通过同构判断来替代,已经在攻击方做过。 novelty = self._novelty_score(candidate, knowledge_base) if novelty < self.novelty_threshold: return False, "新颖性不足" # 价值分数由攻击方给出,裁判方设定收录阈值 if value_score < 0.5: return False, "价值分数未达收录阈值" return True, "收录成功" def _novelty_score(self, candidate, knowledge_base): """ 计算候选图相对于知识库的新颖性。 这里用“结构差异”的简化版本:与已有图的最小编辑距离。 由于精确计算编辑距离成本较高,这里使用节点数和边数差异作为代理指标。 """ if not knowledge_base: return 1.0 min_diff = float("inf") nodes_c = candidate.number_of_nodes() edges_c = candidate.number_of_edges() for existing in knowledge_base: nodes_e = existing.number_of_nodes() edges_e = existing.number_of_edges() diff = abs(nodes_c - nodes_e) + abs(edges_c - edges_e) if diff < min_diff: min_diff = diff # 归一化 score = 1.0 / (1.0 + min_diff) return score class DiscoveryEnvironment: """ 开放世界多智能体环境: 负责组织三个 Agent 的协作循环。 """ def __init__(self, max_rounds=50, knowledge_limit=10): self.graph_space = GraphSpace() self.generator = GeneratorAgent(self.graph_space) self.critic = CriticAgent() self.judger = JudgerAgent() self.knowledge_base = [] self.max_rounds = max_rounds self.knowledge_limit = knowledge_limit self.logs = [] def run(self): """ 运行多智能体数学发现循环。 """ for round_idx in range(self.max_rounds): # 1. 正向方生成候选 candidate = self.generator.generate(self.knowledge_base) # 2. 反向方攻击 attack_result, value_score, attack_reason = self.critic.attack( candidate, self.knowledge_base ) # 3. 裁判方判断 accepted, judge_reason = self.judger.judge( candidate, attack_result, value_score, self.knowledge_base ) # 4. 更新知识库 if accepted: self.knowledge_base.append(candidate.copy()) self.logs.append( f"第 {round_idx + 1} 轮: 接受候选图,节点数={candidate.number_of_nodes()}," f"边数={candidate.number_of_edges()},价值={value_score}," f"原因={judge_reason}" ) else: self.logs.append( f"第 {round_idx + 1} 轮: 拒绝候选图,价值={value_score}," f"攻击原因={attack_reason},裁判原因={judge_reason}" ) # 5. 判断是否达到知识库规模上限 if len(self.knowledge_base) >= self.knowledge_limit: break return self.knowledge_base6.2 文件run_discovery.py
# 文件路径:run_discovery.py from mathematical_discovery import DiscoveryEnvironment def main(): env = DiscoveryEnvironment(max_rounds=50, knowledge_limit=10) knowledge_base = env.run() print("========== 多智能体数学发现运行报告 ==========") print(f"知识库最终收录图数量: {len(knowledge_base)}") print() for log in env.logs: print(log) print() print("========== 知识库中的图结构 ==========") for i, graph in enumerate(knowledge_base): print( f"图 {i + 1}: 节点数={graph.number_of_nodes()}, " f"边数={graph.number_of_edges()}, " f"连通性={nx.is_connected(graph)}" ) if __name__ == "__main__": main()注意:在run_discovery.py中,我们使用了nx.is_connected,所以需要在文件开头导入networkx。
# 修正后的 run_discovery.py 开头 import networkx as nx from mathematical_discovery import DiscoveryEnvironment def main(): env = DiscoveryEnvironment(max_rounds=50, knowledge_limit=10) knowledge_base = env.run() print("========== 多智能体数学发现运行报告 ==========") print(f"知识库最终收录图数量: {len(knowledge_base)}") print() for log in env.logs: print(log) print() print("========== 知识库中的图结构 ==========") for i, graph in enumerate(knowledge_base): print( f"图 {i + 1}: 节点数={graph.number_of_nodes()}, " f"边数={graph.number_of_edges()}, " f"连通性={nx.is_connected(graph)}" ) if __name__ == "__main__": main()7. 运行结果与效果验证
7.1 运行方式
在项目目录下执行:
python run_discovery.py我这边运行一轮(种子固定为 42),输出大致如下:
========== 多智能体数学发现运行报告 ========== 知识库最终收录图数量: 9 第 1 轮: 接受候选图,节点数=6,边数=12,价值=0.5333,原因=收录成功 第 2 轮: 拒绝候选图,价值=0.2,攻击原因=图不连通,裁判原因=攻击方未通过 第 3 轮: 接受候选图,节点数=8,边数=20,价值=0.6083,原因=收录成功 第 4 轮: 拒绝候选图,价值=0.1,攻击原因=价值分数过低,裁判原因=攻击方未通过 ...由于随机种子固定,你在自己的机器上运行也可以得到基本可复现的结果。
7.2 如何判断系统真的在“发现”
判断标准有四个:
第一,看知识库中收录的图是否具有“非平凡结构”。如果所有收录图都是空图或完整图,说明系统没有在探索,只是在随机生成。
第二,看拒绝率和收录率是否在一个合理区间。如果收录率接近 100%,说明评价系统过松,生成的结果没有经过有效筛选;如果收录率接近 0,说明反向方的阈值设得过高,正向方难以生成有效候选。一般来说,收录率在 10% 到 40% 之间比较健康。
第三,看被拒绝的候选是否因“同构”而淘汰。这说明系统能识别“再次发现同一个对象”的情况,体现的是记忆能力。
第四,看迭代过程中收录的图是否有“多样性”。比如节点数和边数的分布不要过度集中在某个点附近,否则说明正向方搜索策略偏窄。
7.3 可视化验证
你还可以把最终知识库里的图画出来,直观检查多样性:
import matplotlib.pyplot as plt import networkx as nx from mathematical_discovery import DiscoveryEnvironment env = DiscoveryEnvironment(max_rounds=50, knowledge_limit=10) knowledge_base = env.run() fig, axes = plt.subplots(2, 5, figsize=(15, 6)) axes = axes.flatten() for i, graph in enumerate(knowledge_base): nx.draw(graph, ax=axes[i], with_labels=True, node_color="lightblue", edge_color="gray") axes[i].set_title(f"Graph {i + 1}") plt.tight_layout() plt.savefig("discovered_graphs.png") plt.show()如果把图画出来,你大概率会看到一些边分布不太均匀但并非完全随机的小图。这些图就是多智能体系统自主构造出来的“候选数学对象”,它们不一定多么深刻,但确实是系统自己发现、并通过了攻击和裁判的筛选。整个流程的意义,不在于某个具体的图多漂亮,而在于“发现—验证—筛选”的自动化闭环已经成立。
7.4 第一次运行失败怎么排查
如果运行报错,先按下面顺序排查:
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
ModuleNotFoundError: No module named 'networkx' | 未安装依赖 | 查看当前 Python 环境 | pip install networkx numpy |
random.randint报错 | 随机种子越界或参数异常 | 查看具体报错堆栈 | 检查GraphSpace.random_seed_graph中的调用 |
| 收录率一直为 0 | 反向方价值阈值过高或生成策略太差 | 打印每一轮的攻击原因 | 降低min_value_threshold,或调整生成策略变异概率 |
| 收录率瞬间达到 100% | 裁判阈值过低 | 查看知识库多样性 | 提高裁判的novelty_threshold或价值收录阈值 |
8. 常见问题与排查思路
除了上面提到的运行失败问题,实际使用中还会有一些更隐蔽的坑。这里集中讲一下。
8.1 多智能体空转:讨论了十几轮,知识库一条没增加
这是最常见的现象。原因通常不是代码 bug,而是反向方的“攻击阈值”设得过高,或者正向方的生成策略与攻击方不对齐。
比如在本次代码中,反向方要求图连通,同时要求价值分数不低于 0.4。如果你把变异操作中的“remove_edge”使用概率调高,那么大量候选图会变成非连通图,收录率就会急剧下降。
排查方式如下:
- 打印每一轮攻击方的拒绝原因,看是否集中在某一类(如“图不连通”)。
- 如果集中在某一类,降低对应评分的占比,或让生成方优先选择能通过该验证的变异操作。
- 最简单的方法是把攻击方的
min_value_threshold降低,但不要完全去掉,否则失去了攻击方的意义。
8.2 知识库全是同构的图
这表明“新颖性”判断失效了。
在真实系统中,同构判断是最基本的去重手段。但对于更大的数学结构,同构判断本身可能非常耗时。更稳妥的方式是引入“图哈希”或“规范化”预处理:在判断同构前,先计算图的weisfeiler_lehman_graph_hash,快速排除明显不同的图;只有在哈希值相同时才调用is_isomorphic。
示例优化如下:
import networkx as nx def canonical_hash(graph): return nx.weisfeiler_lehman_graph_hash(graph) # 在反向方验证前加入哈希去重 def attack(self, candidate, knowledge_base): candidate_hash = canonical_hash(candidate) for existing in knowledge_base: if canonical_hash(existing) == candidate_hash: return False, 0.0, "哈希相同,可能是同构图" if nx.is_isomorphic(candidate, existing): return False, 0.0, "图与知识库中已有图同构" ...这样可以大幅减少同构判断的开销。
8.3 生成方“灵感枯竭”,永远在生成同一类图
如果知识库里全都是节点数 6、边数 12 左右的图,说明生成方陷入了局部搜索。解决思路有两个:
一是增加“突变”概率,让系统有更大机会跳出局部最优。
二是引入“多样性奖励”:裁判在判断新颖性时,不应该只看节点数和边数的差异,还应该考虑图的谱特征、直径、聚类系数等。只有当候选图在多个维度上和已有知识库拉开差距时,才判为高新颖度。
一个简单的谱特征差异计算:
import numpy as np import networkx as nx def spectral_signature(graph): """返回图的拉普拉斯特征值前 k 个,作为结构简化签名。""" lap = nx.laplacian_spectrum(graph) k = min(5, len(lap)) return lap[:k] def spectral_difference(g1, g2): s1 = spectral_signature(g1) s2 = spectral_signature(g2) return np.linalg.norm(np.array(s1) - np.array(s2))这类特征可以将“结构相似但边数不同”的情况也纳入新颖度判断。
8.4 多智能体协作变成互相甩锅
当你把系统接上大语言模型之后,会遇到另一个有趣的问题:正向方生成的猜想明显不靠谱,但反向方也不认真验证,直接说“该猜想有潜力”,裁判最后就收录了一个垃圾结论。
这种“互相吹捧”的原因在于:你给每个 Agent 的 Prompt 里都没有强调“严格验证”的义务,也没有给它们设置对抗激励。
解决办法是在 Prompt 里显式要求:
- 正向方必须为每个猜想附上“为什么新颖”的论证。
- 反向方必须尝试构造至少一个反例,构造不出来才允许给出“暂时通过”。
- 裁判方必须给出可量化的评分维度,不能只给结论。
即使不在代码层面实现完整的大模型接入,这些原则也适用于任何多智能体协作设计。
9. 最佳实践与工程建议
聊完了具体代码和排错,这一节我来提炼一些真正能用在工程和研究里的建议。
9.1 先跑通最小闭环,再追求“真”数学
很多人看到“数学发现”这四个字,就忍不住想去实现群论里的新猜想或数论里的新公式。但作为第一步,我强烈建议你先在一个极其简化的数学空间(比如图、或者有限自动机)上跑通整个多智能体闭环。
原因很简单:多智能体系统里最大的不确定性不是单个智能体的能力,而是智能体之间的交互是否收敛。你需要在最简单的数学空间里观察:正向方是否在有效探索?反向方是否在严格验证?裁判是否在正确筛选?如果这些行为没有形成正循环,换到更复杂的数学空间只会更不可控。
9.2 把“验证器”和“生成器”彻底分离
在写代码时,要保证验证逻辑不依赖于任何智能体的输出。反向方、裁判方引用的应当是“事实校验函数”,而不是“另一个 Agent 的意见”。
这样设计的好处是:如果未来你发现某个验证函数有 bug,只需要修验证器本身,不需要重新考虑整个智能体协作逻辑。同时,验证器也可以被单独测试、单独优化。
在本次示例中,反向方内部的验证函数(连通性检查、同构检查、价值评估)都是纯函数,不读取任何生成器的内部状态。这就是正确的分离方式。
9.3 每一步都要可记录、可回放
数学发现过程本质上是一个搜索过程,你永远不知道哪一步会产出真正有价值的结果。因此,建议在系统里输出结构化的运行日志,至少包含:
- 轮次编号。
- 候选对象的序列化表示(比如图结构 JSON)。
- 各智能体的决策和理由。
- 知识库是否更新,以及更新后的知识库快照。
有了这些日志,你才能做回放和复盘,找到某个有价值结果是哪一轮生成的,它又是怎么通过层层筛选的。
9.4 价值函数不要太“自以为是”
这是最容易被忽视的一点。我们很难提前定义“什么是有价值的数学发现”,所以价值函数最好不要过度设计。
一个稳妥的方案是:只定义“最小值”门槛,不要定义“最大值”目标。也就是说,让裁判只负责过滤明显低质量的对象,而不负责挑选“最优”对象。这样系统的探索方向不会过早被单一评价指标锁死。
如果你过早地用“边数越多越好”“三角形越多越好”这类指标指导搜索,系统会快速收敛到你预设的偏好上,而不是真正探索未知结构。
9.5 关于安全与公平
这里补充几句必要的工程安全提醒。
多智能体数学发现系统本身不涉及高权限操作,但如果将来你把它接入真正的定理证明器、自动推理服务或云端计算集群,请一定注意:
- 下发的代码、SQL、脚本不得直接执行。任何需要执行的外部程序都要经过白名单校验。
- 对自动生成的内容,不要直接信任。数学发现的结果必须经过二次人工或独立验证器复核,再对外发布。
- 如果需要长期运行,设置最大迭代次数和最大内存占用,防止失控循环消耗过多资源。
在科研工作中,多智能体可以辅助发现,但最终负责的仍然是人。把系统当作“自动提出候选假说的助手”,而不是“自动发表论文的作者”,这个边界要清楚。
10. 总结与后续学习方向
这篇文章从“为什么大多数多智能体协作没有真正产生认知增量”这个问题出发,讲了三个核心概念:
第一,多智能体数学发现与自动定理证明的本质区别:前者是“自己出题自己解”,后者是“给定命题证明真假”。
第二,正反博弈 + 裁判架构为什么适合自主发现:因为它构建了“生成—攻击—筛选”的闭环,让系统在开放世界里既能探索,又能纠错,还能沉淀知识。
第三,我们实现了一个不需要大模型、完全可复现的最小系统。你可以在图空间里看到三个智能体如何协作,如何筛选出知识库,以及如何避免生成低价值或重复对象。
如果你想把代码跑通,建议先从修改以下三个参数开始:
min_value_threshold:反向方的攻击门槛。novelty_threshold:裁判方的新颖性门槛。mutation_rate:正向方的探索强度。
建议收藏本文,并在动手写自己的多智能体数学发现系统时,先把这三个参数调出一组肉眼可见的差异来。理解了“生成—攻击—裁判”的角色分工,你再去看市面上的多智能体框架,会很容易判断它到底是在做任务编排,还是在做真正的自主发现。
下一步的深入学习方向有三个:
第一,在数学空间上做扩展。把图换成形式化数学语句,把验证函数换成轻量级的定理证明器(如 Lean、Coq 的 Python 接口),让系统生成并验证真正的数学命题。这是一个难度较大但非常有价值的进阶方向。
第二,在智能体策略上做扩展。不再用简单的随机变异,而是给正向方接入一个小型大语言模型,让它在生成命题时使用类比和组合策略。注意,模型的输出必须经过严格验证器,不能直接进入知识库。
第三,在评价体系上做扩展。引入“知识库压缩率”“猜想被进一步证明的比率”等元指标,衡量多智能体系统本身的科研产出效率。
最后提醒一点:数学发现是一个开放性问题,不要指望一个多智能体系统几天就能发现黎曼猜想级别的结论。这套架构真正的价值,是让“提出猜想—验证猜想—筛选猜想”这个过程变成可编程、可观测、可迭代的自动化流水线。它不会取代数学家,但它是未来“人机协同科研”最值得先搭起来的一块地基。