Stephen Diehl Stephen Diehl

使用模型上下文协议(Model Context Protocol)进行符号代数探险

上周末我花了一些时间研究这个新的 MCP protocol,这是现在很热门的技术。它很有趣,但也还处于早期阶段,存在一些粗糙的地方。这个项目的源代码已经上传到 Github,如果有人想尝试一下,可以去看看。

如果你还不熟悉 MCP,它是由 Anthropic 提出的,旨在解决这个问题:“我们如何让 LLM 真正地事情,而不是仅仅谈论做事情?”它是一个协议,允许语言模型调用外部工具,就像你那个号称什么都知道的朋友,其实是在背后打电话给更懂行的朋友一样。MCP 的核心思想是标准化语言模型调用外部工具的方式。通过这种方式,不再需要为每个 AI 模型和每个工具编写自定义连接器,只需在每一端实现一次该协议即可。如果说 USB-C 还在尴尬的青春期,那它就是 AI 工具的 USB-C。

值得注意的是,MCP 服务器在本地运行,允许语言模型通过使用 LLM 桌面客户端调用本地服务器来调用任意代码和命令。这通常可能不是一个好主意,并且可能像你想象的那样危险和鲁莽,因此这里存在明显的安全问题,请注意。但让我们不要让这些影响这个有趣的周末实验。

我的兴趣源于观察到 Claude(以及它的同类,如 o4-mini-high 和 DeepSeek-R1)在张量微积分方面表现得非常挣扎。如果你曾经要求 LLM 执行复杂的符号操作,你就会明白:自信的回答,漂亮的 LaTeX 格式,以及让你的数学教授泪流满面的结果。尽管这些模型具有令人印象深刻的语言能力,但在跟踪张量表达式中的索引或操作复杂的代数形式方面却一塌糊涂。即使是中等难度的广义相对论问题也涉及到巨大的表达式,包含数百个项和复杂的 index gymnastics

但是我们已经有擅长此道的专业工具!像 Mathematica、Sympy、xAct、Cadabra 这样的计算机代数系统就是专门为此而构建的。因此,一个显而易见的解决方案出现了:让我们通过 MCP 将这些工具暴露给 LLM,让每个系统做它最擅长的事情。LLM 处理自然语言理解和规划,而符号代数系统则以完美的精度执行实际的数学运算。

使用 MCP 生态系统就像参观狂野西部的边境小镇。文档以零星的篝火故事形式存在,实现方式具有明显的“我在凌晨 3 点的黑客马拉松上写了这个”的风格,而且一切都非常依赖 Node。这种对 Node 的偏爱可能源于大多数 MCP 工具都被设计为为云应用程序调用 REST 服务。然后是由恰好销售 AI 编码助手的公司提供的,具有令人怀疑的自我参照产品的特殊生态系统。空气中确实弥漫着淡淡的投机主义味道。

调试 MCP 服务器是一项疯狂的练习。你本质上是在使用一个随机的黑盒子,它通过连接到 docstring 注释的复杂 JSON 模式网络进行通信。当出现问题时,祝你好运,你要弄清楚是你的服务器、客户端、LLM 的解释,还是仅仅是模型在嘲笑你。整个设置的非确定性意味着某件事可能连续五次完美运行,然后在第六次尝试时自发失败,因为推理服务器上的随机数种子不同,prompt 的 vibes 不对,或者由于完全隐藏在数字神秘主义中的原因。欢迎来到软件的有趣未来。

然而,一旦你进行了仪式性的动物牺牲以安抚非决定论之神(我发现牺牲一只橡皮鸭,同时吟唱“Ph'nglui mglw'nafh Roko's Basilisk R'lyeh wgah'nagl fhtagn, O Great Transformer of the Deep, may thy hallucinations be few and thy completions true”效果最好),你就会发现基本实现非常简单,类似于 FastAPI,如果 FastAPI 为语言模型设计其 endpoints 的话。这是一个简单的例子,它突出了这种方法的重要性:要求任何 LLM 对一个大整数进行因式分解,并观察它自信地捏造完全错误的答案。从设计上讲,transformers 无法执行整数分解所需的任意计算。它们只是从互联网上记忆了一些因式分解(即使是这些,也很糟糕)。

from mcp.server.fastmcp import FastMCP
import subprocess
mcp = FastMCP("Demo", instructions="You factor integers.")
def factor_number(number):
  result = subprocess.run(['factor', str(number)], capture_output=True, text=True)
  return result.stdout.strip()
@mcp.tool()
def factor(a: int) -> int:
  """Factor an integer"""
  return factor_number(a)

有了这个简单的工具,我们现在可以使用 GNU factor shell 命令将实际的计算暴露给 LLM。模型不再编造因子,而是识别何时将任务委派给专门的工具。

当它工作时,它非常神奇。这些组件的成功集成(格式正确的 JSON、语言模型准确的工具选择和可靠的服务器处理)证明了这种架构方法的潜力。虽然这项技术仍处于早期开发阶段,但与形式验证系统集成的可能性尤其引人注目。LLM 与 theorem provers like Lean 的结合可以实现形式数学的自然语言接口,使自动化推理系统能够受益于语言模型理解非正式数学概念的能力,同时保持严格的逻辑基础。同样,通过自然语言访问计算机代数系统可以大大降低复杂数学计算的门槛。然而,在确保这些不同系统之间可靠且语义正确的交互方面,仍然存在重大的研究挑战。

让我用一个来自物理学的经典例子来展示这在实践中是如何运作的:求解具有驱动项的阻尼谐振子。这是本科物理学的基本内容,描述如下:

md2xdt2+cdxdt+kx=F(t)

其中 m 是质量,c 是阻尼系数,k 是弹簧常数,F(t) 是一个随时间变化的外力。

以下是使用我们的 sympy-mcp 服务器的工作流程:

Assistant : (内部工具调用) intro_many 引入具有适当假设的变量:

{
 "variables": [
  {"var_name": "t", "pos_assumptions": ["real"], "neg_assumptions": []},
  {"var_name": "m", "pos_assumptions": ["real", "positive"], "neg_assumptions": []},
  {"var_name": "c", "pos_assumptions": ["real", "positive"], "neg_assumptions": []},
  {"var_name": "k", "pos_assumptions": ["real", "positive"], "neg_assumptions": []}
 ]
}

Assistant : (内部工具调用) introduce_function 定义 x(t) 为函数:

{
 "func_name": "x"
}

Assistant : (内部工具调用) introduce_expression 创建微分方程:

{
 "expr_str": "Eq(m*Derivative(x(t), t, t) + c*Derivative(x(t), t) + k*x(t), F(t))"
}

Assistant : (内部工具调用) introduce_function 定义驱动函数 F(t):

{
 "func_name": "F"
}

Assistant : (内部工具调用) dsolve_ode 求解 ODE:

{
 "expr_key": "expr_12",
 "func_name": "x"
}

瞧!LLM 没有产生幻觉解决方案,而是委托给 SymPy,后者返回正确的解决方案:

x(t)=C1et(−c+c2−4km)2m+C2e−t(c+c2−4km)2m+et(−c+c2−4km)2m∫F(t)ect2me−tc2−4km2m,dtc2−4km−e−t(c+c2−4km)2m∫F(t)ect2metc2−4km2m,dtc2−4km

没有幻觉项,没有凭空出现的神秘常数,只有正确的解决方案。LLM 处理自然语言交互和编排,而计算机代数系统则做它最擅长的事情……精确的符号操作。

无论如何,所有代码都已上传到 Github here,所以也许其他人会觉得它有用。如果您安装了 Cursor 或 Claude,请将以下内容添加到 ~/.cursor/mcp.json~/Library/Application Support/Claude/claude_desktop_config.json 以安装 MCP 服务器。

{
 "mcpServers": {
  "sympy-mcp": {
   "command": "uv",
   "args": [
    "run",
    "--with",
    "https://github.com/sdiehl/sympy-mcp/releases/download/0.1/sympy_mcp-0.1.0-py3-none-any.whl",
    "python",
    "server.py"
   ]
  }
 }
}

或者,稍微好一点的是,从 Docker 镜像运行它。

{
 "mcpServers": {
  "sympy-mcp": {
   "command": "docker",
   "args": [
    "run",
    "-i",
    "-p",
    "8081:8081",
    "--rm",
    "ghcr.io/sdiehl/sympy-mcp:latest"
   ]
  }
 }
}

并且也许阅读一下您正在安装的源代码,因为我觉得很多人会在不知道他们在做什么的情况下安装这些 MCP 服务器,从而安装大量的恶意软件和漏洞。这里基本上没有任何安全性。这可能是一个大问题。请谨慎行事。

© 2009 - 2025 Stephen Diehl. 版权所有。