Lambda演算运行时

一个基于JSON的Lambda演算运行时,支持元编程和动态工具演化,适用于语言模型的计算需求。

  • by hungryrobot1
  • Haskell
  • 本地部署
  • 57 星标
  • 更新于
在 GitHub 查看 hungryrobot1/MCP-PIF

使用说明

MCP-PIF(注:这是一个专业术语或特定缩写,根据上下文可能有不同的具体含义,但在此直接翻译为中文保持原样)

一个支持元循环评估的原生JSON Lambda演算运行时,设计为MCP(模型上下文协议)服务器。它使语言模型能够通过元编程动态演化工具。

快速入门

# Build the project
cabal build

# Enable debug mode for detailed evaluation tracing
MCP_DEBUG=1 cabal run mcp-pif
# Debug output (to stderr) shows:
# - Each evaluation step
# - Environment keys at each step
# - Closure creation and application
# - Tool code lookups

基本示例

// Create a tool
{
  "jsonrpc": "2.0",
  "method": "tools/call",
  "params": {
    "name": "evolve",
    "arguments": {
      "name": "square",
      "description": "Squares a number",
      "code": {"lam": "x", "body": {"mul": [{"var": "x"}, {"var": "x"}]}}
    }
  }
}

// Use the tool
{
  "jsonrpc": "2.0",
  "method": "tools/call",
  "params": {
    "name": "run",
    "arguments": {
      "tool": "square",
      "input": 7
    }
  }
}
// Returns: 49

// Get help
{
  "jsonrpc": "2.0",
  "method": "tools/call",
  "params": {
    "name": "help",
    "arguments": {"category": "lists"}
  }
}
// Returns: Documentation for list primitives

核心概念

问题

对于纯粹的计算语言模型来说,需要计算工具,但现有的系统要么提供固定的API,要么允许无限制的代码执行。在元编程和自修改的背景下,这两种方式都不理想。本程序提出了一种折中结构,以实现安全、可检查、可演化的计算。

从理想意义上讲,MCP-PIF可以被视为一种通用的、可变形的计算机接口,其中语言模型作为执行函数被集成进去。在这里,我们提供了一组简单的λ演算原语词汇,仅用于访问纯计算功能。

解决方案

MCP-PIF 提供了一种带有三个元编程原语的 λ 演算:

  • quote - 将代码视为数据(防止求值)
  • eval - 动态执行带引号的代码
  • code_of - 反思任何工具的源代码

这创造了一个 元循环的 这是一个系统,其中工具能够分析并转换其他工具,同时保持确定性和燃料(资源)受限的执行。它具有同像性,但受到限制。

值得注意的是 quote 和 eval 并不完全对称:

-- eval cleans the environment:
let cleanEnv = M.filterWithKey (\k _ -> not $ k `elem` ["__tool_name", "__self"]) env

这防止了通过 eval 执行的代码继承错误的工具上下文。当 eval 执行引用的代码:

  • 用户变量会被保留 (保持词法作用域)
  • 系统变量已清除 (__tool_name 和 __self (已移除以避免工具上下文混淆)
  • 工具代码仍然可用 (for code_of 内省
  • __eval_depth 计数器已添加 (最大深度:100,防止无限评估循环)

事件视界

由于这个框架是基于MCP构建的,因此在纯粹性上做出了重大妥协。具体来说,更新服务器代码并不是元编程循环的一部分。这意味着,基本元素列表、解析策略以及进化和评估过程本身在运行时是不可修改的。

有两种工具:

  • MCP工具例如,通过……创建 evolve (有副作用,会修改注册表)
  • 进化的工具通过工具执行 run (纯粹的,功能性的)

进化后的工具可以相互交互,但在无法访问协议级工具注册表的情况下,它们自身无法进化出新的工具。这种“拟像”实现方式通过隔离那些使丰富元编程成为可能的不变性,防止了无限制的自我修改。

语言参考

核心基本元素

类别 原始类型 JSON 语法 描述
Lambda 变量 {"var": "x"} 变量引用
功能 {"lam": "x", "body": ...} Lambda 抽象
应用 {"app": {"func": ..., "arg": ...}} 函数应用
算术 添加 {"add": [a, b]} 加法
减去 {"sub": [a, b]} 减法
乘以 {"mul": [a, b]} 乘法
分割 {"div": [a, b]} 除法(对0的错误)
模块 {"mod": [a, b]} 模块(0时的错误)
比较 等于 {"eq": [a, b]} 等值测试
少于 {"lt": [a, b]} 少于
小于或等于 {"lte": [a, b]} 小于或等于
大于 {"gt": [a, b]} 大于
大于或等于 {"gte": [a, b]} 大于或等于
逻辑 和 {"and": [a, b]} 逻辑与(短路)
或者 {"or": [a, b]} 逻辑或(短路)
否 {"not": a} 逻辑非
控制 如果 {"if": {"cond": c, "then": t, "else": e}} 条件
继续 {"continue": {"input": x}} 递归步骤
列表 无 {"nil": true} 空列表
缺点 {"cons": {"head": ..., "tail": ...}} 列表构建
折叠 {"fold": [func, init, list]} 通用减速器(配对参数详情请参见GUIDE.md文件)
成对(或配对) 配对 {"pair": [a, b]} 配对构造
第一 {"fst": pair} 获取第一个元素
第二 {"snd": pair} 获取第二个元素
元(Meta) 引用 {"quote": term} 防止评估
评估 {"eval": quoted} 执行引用代码
代码表 {"code_of": "tool_name"} 获取工具源码
自己 {"self": true} 当前闭包引用(仅限工具)

字面量

  • 数字好的,我来翻译一下。 42, -17, 3.14 → 整数(浮点数四舍五入后)
  • 布尔值(或布尔类型): true, false
  • 字符串: "hello", "world"
  • 数组: [1, 2, 3] → 转换为连接列表(或称为表连接)
  • 空(或“无”): null → 单位价值

输入归一化

这个(或“该”) run 该工具会自动对输入进行规范化处理,以使命令行界面(CLI)的使用更加人性化:

  • 字符串数字: "42" → 42
  • 布尔字符串: "true" → true, "false" → false
  • JSON 字符串: "{\"x\": 5}" → 解析为JSON对象
  • 列表JSON 数组被转换为 cons 列表

这允许灵活的输入格式,同时在评估过程中保持类型安全。

快速参考

如需查看详细模式和示例,请参阅 用户指南。

需要记住的关键概念

  1. 工具参考工具可以作为字符串进行引用("square"), 内联lambda表达式,或通过 code_of
  2. 评估范围界定用户变量被保留,系统变量被清除,工具代码仍然可用
  3. 折叠签名`fold` 函数接收一个单一的配对 (accumulator, item),不是两个参数
  4. **继续(或延续)**仅使用注册过的工具,每一步都是一个MCP往返过程
  5. 自我指涉: {"self": true} 仅在已注册工具中有效 (通过以下方式创建: evolve), 不在内联 lambda 中 传递给 run
  6. 事件视界在λ演算中,工具无法创建其他工具

递归快速参考

需要递归吗? 使用这个决策树:

Can you structure it with an accumulator?
├─ Yes → Use `continue` (works for any depth)
│         Pattern: take pair [state, accumulator]
│         Base case: return accumulator
│         Recursive: compute new accumulator, continue with [new_state, new_acc]
│
└─ No, need result immediately?
   ├─ Small input (n < 20) → Use `self`
   └─ Large input → Redesign with accumulator or use fold

示例:

  • 阶乘 → continue 使用累加器模式
  • 总和 → continue 使用累加器模式
  • 斐波那契数列(小n)→ self
  • 偶数/奇数相互递归 → eval 加号 code_of

模式与示例

递归阶乘

工具可以使用基于延续的递归来逐步执行。由于 continue 暂停评估并将控制权返回给MCP层时,你必须使用累加器模式,其中计算在递归过程中进行,而不是在递归之后:

{
  "name": "evolve",
  "arguments": {
    "name": "factorial",
    "description": "Computes factorial using continuation with accumulator",
    "code": {
      "lam": "n_acc",
      "body": {
        "if": {
          "cond": {"lte": [{"fst": {"var": "n_acc"}}, 1]},
          "then": {"snd": {"var": "n_acc"}},
          "else": {
            "continue": {
              "input": {
                "pair": [
                  {"sub": [{"fst": {"var": "n_acc"}}, 1]},
                  {"mul": [{"fst": {"var": "n_acc"}}, {"snd": {"var": "n_acc"}}]}
                ]
              }
            }
          }
        }
      }
    }
  }
}

用法:

{
  "name": "run",
  "arguments": {
    "code": "factorial",
    "input": {"pair": [5, 1]}
  }
}

该程序将返回一个结构化的响应:

{
  "type": "continuation",
  "message": "Recursive step needed. Call run again with:",
  "tool": "factorial_acc",
  "next_input": {
    "pair": [4, 5]
  },
  "step": 1
}

这为客户呈现了一个Haskell表示形式:

Object (fromList [("message",String "Recursive step needed. Call run again with:"),("next_input",Object (fromList [("pair",Array [Number 4.0,Number 5.0])])),("step",Number 1.0),("tool",String "factorial_acc"),("type",String "continuation")])

重要提示: 这个工具接受一对(数据/参数) [n, accumulator] 作为输入。从……开始 [5, 1] 计算5!。每一步递归都将累加器乘以当前的n,然后将n减1。

为什么是这种模式?

该 continue 原生函数并不返回一个你可以用以进行计算的值,而是返回一个延续标记。所有的计算都必须在调用之前完成 continue存储在累加器中。模式是:

  • 输入: [n, acc] 哪里 acc 保存部分结果
  • 基本情况: 当 n ≤ 1,返回累加器
  • 递归情况: 计算新的累加器(n * acc),继续进行 [n-1, new_acc]

备选方案:使用直接递归 self (燃料限制):

{
  "name": "evolve",
  "arguments": {
    "name": "factorial_self",
    "description": "Simple factorial using self (small n only)",
    "code": {
      "lam": "n",
      "body": {
        "if": {
          "cond": {"lte": [{"var": "n"}, 1]},
          "then": 1,
          "else": {
            "mul": [
              {"var": "n"},
              {"app": {"func": {"self": true}, "arg": {"sub": [{"var": "n"}, 1]}}}
            ]
          }
        }
      }
    }
  }
}

这种方法对于小规模输入有效,但在n≈20时会达到燃料限制(10,000步)。

通过折叠实现高阶映射

{
  "name": "map",
  "description": "Maps a function over a list",
  "code": {
    "lam": "f",
    "body": {
      "lam": "list",
      "body": {
        "fold": [
          {"lam": "acc_item", "body": {
            "cons": {
              "head": {"app": {"func": {"var": "f"}, "arg": {"snd": {"var": "acc_item"}}}},
              "tail": {"fst": {"var": "acc_item"}}
            }
          }},
          {"nil": true},
          {"var": "list"}
        ]
      }
    }
  }
}

元编程:代码分析

{
  "name": "count_operations",
  "description": "Counts arithmetic operations in a tool",
  "code": {
    "lam": "tool_name",
    "body": {
      "eval": {
        "quote": {
          "analyze": [{"code_of": {"var": "tool_name"}}]
        }
      }
    }
  }
}

该 code_of `primitive` 返回工具的源代码作为引用数据,从而支持程序分析和转换。

建筑学

管道

JSON Input → Parser → Term → Evaluator → RuntimeValue → Encoder → JSON Output
         validation  syntax    execution     values     serialization

模块

模块 用途
Main.hs 入口点,JSON-RPC 循环
Server.hs 翻译为中文是“服务器.hs”(但通常我们不会直接这样翻译文件名,而是保持原样,因为.hs是Haskell编程语言的源代码文件扩展名,所以更自然的表达可能是“Server.hs(Haskell源代码文件)”)。不过,在大多数情况下,文件名会保持不变,直接称为“Server.hs” MCP协议,请求路由
Core/Parser.hs 翻译为中文是:核心/解析器.hs JSON → 术语验证
Core/Evaluator.hs 翻译为中文是:核心/评估器.hs 以燃料为条件的条款执行
Core/Encoder.hs 翻译为中文是:“核心/编码器.hs” 运行时值 → JSON
Core/Types.hs 翻译为中文可以是“核心/类型.hs”或“核心模块/类型定义.hs”,具体取决于上下文和该文件在项目中的具体用途。在这里,“Core”通常指的是程序或系统的核心部分,“Types”则指的是类型定义或类型相关的代码。而“.hs”是Haskell编程语言的源代码文件扩展名。所以,这个文件名可以理解为“核心模块中的类型定义文件”或“核心部分的类型相关代码文件” 核心类型定义
Core/Syntax.hs 翻译为中文是:“核心/语法.hs”。不过,这里的“Core”和“Syntax.hs”通常是编程语言或编译器开发中的术语,具体翻译可能需要根据上下文或项目背景来调整。但直接翻译的话,就是上述结果 术语 ADT
Tools/Registry.hs 翻译为中文是:“工具/注册表.hs” 工具存放处

安全保证

  • 基于燃料的终止每次评估都有有限的步骤(默认:10,000步)
  • 纯粹的评价在λ演算中没有输入/输出或效果
  • 验证后的执行只有结构上有效的术语才能运行
  • 不可变注册表工具在执行过程中不能相互修改

MCP集成

MCP-PIF 实现了用于工具发现和执行的模型上下文协议:

系统工具

  • evolve - 创建新工具(在注册表中存储)
  • run - 执行工具或内联lambda表达式
  • list - 显示所有已注册的工具
  • help - 显示基本元素和系统工具的文档

协议流程

  1. 客户端向标准输入发送JSON-RPC请求
  2. 服务器解析并路由到相应的处理程序
  3. 对于工具执行:
    • 解析输入JSON → 术语
    • 将工具代码注入到环境中
    • 在燃料限制下进行评估
    • 编码结果 → JSON
  4. 响应已发送到标准输出

连接MCP客户端

# Example using Python MCP SDK
import mcp

async with mcp.Client() as client:
    await client.connect(stdio_transport("cabal run mcp-pif"))

    # Create a tool
    await client.call_tool("evolve", {
        "name": "double",
        "description": "Doubles a number",
        "code": {"mul": [{"var": "x"}, 2]}
    })

    # Use it
    result = await client.call_tool("run", {
        "tool": "double",
        "input": 21
    })
    print(result)  # 42

未来方向

MCP-PIF的当前设计在纯计算与具有副作用的操作之间保持了清晰的界限。我们已经考虑了几种扩展方式,这些方式将以有趣的方式拓展这些界限:

分析函数

当前系统主要是 合成的 - 使用基本元素来组合新函数。一个自然的扩展就是 分析性的 能力:

  • 验证未进行评估的期限结构静态分析
  • 规范化将项简化为规范形式
  • 等价性检查证明两个项计算的是同一个函数
  • 类型推断为lambda项推导类型
  • 复杂性分析估算燃料需求
  • 能力分析按基本元素分解工具

这些分析函数将对作为数据的代码(引号中的术语)进行操作,并可能实现强大的元编程模式。然而,它们需要经过精心设计,以在保持核心演算简洁性的同时,提供有意义的保证。

高效的原语(或基本操作)

另一个方向涉及有控制地引入影响:

系统交互:

  • 文件输入/输出原语(readFile, writeFile)
  • 过程控制(exec, env)
  • 网络运营(fetch, serve)

设计挑战:

  • 如何维护纯洁的界限?
  • 效果应该是单子式的、代数式的,还是基于延续的?
  • 如何处理错误和资源管理?
  • 能力控制采用哪种安全模型?

一种方法可能是 基于能力的安全工具可以在创建时声明所需的能力(如文件访问、网络等),而MCP层则负责强制执行访问控制。这与进化工具应承担证明其自身有效性的理念是一致的。

自托管与自举启动

终极的元循环目标:在MCP-PIF自身中实现MCP-PIF的评估器。这将需要:

  • 用于JSON操作的基本函数/方法
  • 模式匹配构造
  • 环境的有效表示
  • 元级别的燃料管理

一个自托管的PIF(编程接口框架)能够实现评估策略本身的运行时进化——一个真正具有自省能力的系统。

协议扩展

MCP边界可以支持额外的协议级操作:

  • 工具版本管理跟踪工具随时间的演变
  • 工具构成工具融合的协议级组合器
  • 分布式注册表在MCP服务器之间共享工具
  • 证明证书为工具附加正确性证明

这些扩展在保持事件视界原则的同时,增强了协议层的功能。

任何扩展的关键设计原则是:保持PIF作为语言模型计算可靠基底的简洁性和可预测性。

许可证

麻省理工学院(MIT)