Hotdry.
ai-systems

LLM作为软件函数组件:类型签名、API契约与函数组合模式

面向Software 3.1架构范式,探讨将LLM输出视为软件函数组件的工程化方法,给出类型签名设计、API契约形式化与函数组合模式的具体参数。

当我们谈论 Software 3.1 范式时,核心转变在于将大语言模型从「对话界面」提升为「可组合的软件函数」。传统软件开发中,每个模块都有明确的输入输出类型和契约约束;如今,这种工程化思维同样适用于 LLM 驱动的智能系统。本文将从类型签名、API 契约和函数组合三个层面,阐述如何将 LLM 视为软件工程中的可组合组件。

一、核心类型签名:从概率性函数到 Effectful 视图

在类型论视角下,一次 LLM 调用并非传统的纯函数,而是一个概率性函数。设请求为 R、配置为 C、执行前状态为 S,则一次调用的类型签名可以形式化为:

LLM : R × C × S → D(O × S')

其中 R 包含提示词、消息历史和工具模式定义;C 包含模型标识、温度参数、最大 token 数等配置;S 表示调用前的系统状态(配额、会话上下文、工具注册表);O 是输出 payload(文本 token、工具调用或结构化 JSON);S' 是调用后更新的系统状态;而 D 表示输出上的概率分布 —— 这正是 LLM 与确定性函数的核心区别。

如果将上述签名封装为更符合工程实践的形式,可以采用 Effectful 计算风格:

LLM : Request → Config → State → IO(Response × State)

这种视角将 LLM 视为产生副作用的 IO 操作,Response 是从底层分布中采样得到的具体实例。在实际 SDK 设计中,这一层抽象通常被简化为异步函数调用,类型签名表现为:LLM : LLMRequest → Promise<LLMResponse>

二、API 契约:TypeScript 类型定义与 Design-by-Contract

请求与响应类型定义

一个实用的 API 契约需要在 JSON Schema 层面固定 Request 和 Response 的结构。以下是典型的类型定义示例:

type ToolSchema = {
  name: string;
  description?: string;
  parameters: JSONSchema;
};

type LLMRequest = {
  messages: Message[];
  tools?: ToolSchema[];
  config?: {
    model: string;
    temperature?: number;
    max_tokens?: number;
  };
  state?: StateHandle;
};

type LLMToolCall = {
  name: string;
  arguments: unknown;
};

type LLMResponse = {
  messages: Message[];
  tool_calls?: LLMToolCall[];
  usage?: {
    prompt_tokens: number;
    completion_tokens: number;
    total_tokens: number;
  };
  newState?: StateHandle;
};

上述类型定义构成了 LLM 函数签名的具体形式。关键在于将模型视为 API 边界上的纯函数,尽管其内部行为在实际运行中是概率性和有状态的。

前后置条件形式化

将 Design-by-Contract 思想引入 LLM 调用,可以为每个函数调用附加形式化的前置条件和后置条件。前置条件约束输入的合法性:messages 必须符合允许的角色(system、user、assistant、tool)和格式规范;总体 token 长度必须落在模型上下文窗口范围内;tools 中的 parameters 必须是合法的 JSON Schema;temperature 取值范围为 [0, 2]。后置条件则保证输出的可预期性:每个 tool_call 的 arguments 必须通过对应工具的 JSON Schema 验证;若 messages 中声明了 format: JSON 约束,assistant 最终消息必须能解析为有效 JSON 并符合指定 schema;usage 字段间需满足内部一致性。

可以将一个完整的 LLM API 契约形式化为五元组 C = ⟨I, Pre, Post, S, π⟩,其中 I 是接口(Request、Response 对),Pre 是前置条件集合,Post 是后置条件集合,S 是状态空间,π 是由模型决定的隐式输出分布。

三、函数组合模式:LLM Router 与工具函数的顺序组合

在 Agent 架构中,LLM 的角色往往不是直接生成最终答案,而是作为「路由器」或「规划器」,决定在何时调用何种工具。工具函数本身是确定性函数:type Tool = (args: A) => Promise<R>;而 LLM 则输出工具调用指令。这种模式可以形式化为组合结构。

组合流程如下:LLM 决策生成 tool_call;系统校验 arguments 是否符合工具的 JSON Schema;若校验通过,调用对应工具函数;将工具返回结果追加到消息历史,再次调用 LLM 生成下一步。从类型签名角度看,单个 Agent Step 可以表示为 AgentStep : (H, S) → IO ((H', S')),其中 H 是消息历史,Agent 内部通过组合 LLM 和工具函数完成这一转换。

当引入 function calling 时,LLM 的类型变成了高阶类型:它消费函数类型签名并发出函数调用实例。ToolExecutor 作为工具执行器,ToolEnhancedLLM 则将 LLM 和执行器组合在一起。概念上,LLM 是一个作用于函数的函数,它接收工具签名列表并决定调用哪个。

四、顺序组合规则与契约兼容性

当我们希望从形式化角度推理多个 LLM / 工具步骤的组合时,可以将每个步骤视为带契约的 Effectful 函数。设 C1 为「LLM → 工具调用 JSON」的契约,C2 为「tool (args) → 结果」的契约,C3 为「LLM (history + tool result) → 最终 JSON」的契约。

假设 Post1 保证 args 符合工具输入 schema,Pre2 恰好要求该 schema;Post2 保证 result 符合某个输出 schema,而 Pre3 恰要求该 schema 出现在历史记录中。那么组合后的管道满足一个导出的契约:C = C1 ; C2 ; C3,其中 PreC = Pre1,PostC = Post3。这种基于契约兼容性的组合推理方式,使得 LLM 加工具的复杂工作流可以在不依赖临时提示词的情况下被形式化验证。

五、实践参数与监控要点

将理论框架落地到工程实践时,以下参数值得关注。模型选择与配置方面,建议在生产环境固定 model 版本而非使用 latest 别名;temperature 默认值设为 0.7,针对需要确定性的任务可降至 0.1 以下;max_tokens 需根据输出 schema 预估并留有冗余。契约验证方面,每次 LLM 返回 tool_calls 前必须执行 JSON Schema 校验,校验失败应触发重试或降级而非直接传递给下游;结构化输出场景推荐使用强制 JSON 模式而非依赖提示词。监控指标应覆盖 token 消耗量(用于成本控制)、工具调用成功率、契约校验失败率以及端到端延迟分布。

通过将 LLM 视为带有类型签名和 API 契约的软件函数组件,开发者可以在保持灵活性的同时获得工程化的可预期性与可组合性。这一思路正是 Software 3.1 架构范式的核心价值所在。


参考资料

  • Contracts for Large Language Model APIs (Tanzim Hossain Romel)
  • LAPIS: Lightweight API Specification for Intelligent Systems (arXiv)
查看归档