五大 LLM 抽象层框架设计理念对比
Related topics: [[kosong]], [[republic]], [[litai]], [[pydantic-ai]], [[langchain]]
概述
本文对比分析五个 LLM 抽象层框架的底层设计理念和架构模式:
| 框架 | 定位 | 核心哲学 |
|---|---|---|
| kosong | LLM 抽象层 | Protocol-based,流式消息合并,为 Agent 设计 |
| Pi AI | 实时交互抽象层 | 事件驱动快照,双模接口 (Stream/Promise),详尽的兼容性契约 |
| republic | Tape-first LLM 客户端 | 审计优先,any-llm 基础,结构化结果 |
| litai | LLM Router | 最小化框架,背景加载,统一计费 |
| pydantic-ai | Agent Framework | FastAPI 风格,类型安全,模型能力配置 |
| LangChain | 全能 LLM 框架 | Runnable 统一接口,LCEL 链式编排,回调驱动 |
1. kosong - Protocol-based 流式抽象
核心设计理念
为现代 AI Agent 应用设计的轻量级抽象层,强调:
- Vendor Lock-in 免疫:通过 Protocol 定义接口,而非继承
- 流式优先:所有消息都以流式方式处理,自动合并片段
- 异步原生:从底层支持异步工具编排
架构亮点
1.1 Protocol-based Provider 接口
@runtime_checkable
class ChatProvider(Protocol):
"""The interface of chat providers."""
name: str
@property
def model_name(self) -> str: ...
async def generate(
self,
system_prompt: str,
tools: Sequence[Tool],
history: Sequence[Message],
) -> "StreamedMessage": ...
设计意图:
- 不强制继承,任何实现 Protocol 的类都是合法 Provider
runtime_checkable支持运行时类型检查- 接口简洁,只有核心方法
1.2 流式消息片段合并机制
class MergeableMixin:
def merge_in_place(self, other: Any) -> bool:
"""Merge the other part into the current part."""
return False
class TextPart(ContentPart):
@override
def merge_in_place(self, other: Any) -> bool:
if not isinstance(other, TextPart):
return False
self.text += other.text # 就地合并
return True
独特设计:
- 流式响应的片段自动合并,上层无需处理片段分割
merge_in_place返回 bool 表示是否成功合并- 支持 TextPart、ThinkPart、ToolCall 的合并
1.3 统一的流式消息抽象
type StreamedMessagePart = ContentPart | ToolCall | ToolCallPart
@runtime_checkable
class StreamedMessage(Protocol):
def __aiter__(self) -> AsyncIterator[StreamedMessagePart]: ...
@property
def id(self) -> str | None: ...
@property
def usage(self) -> "TokenUsage | None": ...
优势:
- 统一处理文本、思考内容、工具调用
- TokenUsage 细分为
input_other,input_cache_read,input_cache_creation - 支持延迟获取 usage 信息
1.4 异步工具编排
async def step(...) -> "StepResult":
tool_result_futures: dict[str, ToolResultFuture] = {}
async def on_tool_call(tool_call: ToolCall):
result = toolset.handle(tool_call)
if isinstance(result, ToolResult):
future = ToolResultFuture()
future.set_result(result)
tool_result_futures[tool_call.id] = future
else:
result.add_done_callback(future_done_callback)
tool_result_futures[tool_call.id] = result
特点:
- 工具调用立即返回 Future,不阻塞消息流
- 支持同步和异步工具的统一处理
- 取消时自动清理所有 pending futures
2. Pi AI (pi-mono) - 事件驱动与兼容性契约
核心设计理念
为高性能实时交互设计的 LLM 抽象层,强调:
- UI/Agent 友好:事件流原生携带最新快照,降低消费端复杂度。
- 双模消费:同一个对象支持 AsyncIterator 流式处理和 Promise 最终结果获取。
- 极致兼容性:通过详尽的兼容性标记 (Compat Flags) 抹平不同 Provider 的碎片化实现。
架构亮点
2.1 携带快照的 AssistantMessageEvent
export type AssistantMessageEvent =
| { type: "text_delta"; delta: string; partial: AssistantMessage }
| { type: "thinking_delta"; delta: string; partial: AssistantMessage }
| { type: "toolcall_delta"; delta: string; partial: AssistantMessage }
| { type: "done"; message: AssistantMessage };
设计便利性:
- 每个增量事件都包含
partial字段,即当前已组装好的完整消息快照。 - Agent 或 UI 层无需自行维护
fragments数组和字符串累加逻辑,极大地减少了状态同步错误。
2.2 EventStream 的双模接口
export class EventStream<T, R> implements AsyncIterable<T> {
// 1. 支持异步迭代 (Streaming)
async *[Symbol.asyncIterator](): AsyncIterator<T> { ... }
// 2. 支持 Promise 获取最终结果 (Completing)
result(): Promise<R> { return this.finalResultPromise; }
}
设计便利性:
- 允许 Agent 循环同时启动流式显示和后续逻辑等待:
const s = stream(); for await (const e of s) { render(e); } const final = await s.result();。
2.3 详尽的兼容性契约 (OpenAICompletionsCompat)
export interface OpenAICompletionsCompat {
supportsReasoningEffort?: boolean;
requiresToolResultName?: boolean;
requiresThinkingAsText?: boolean;
requiresMistralToolIds?: boolean;
thinkingFormat?: "openai" | "zai" | "qwen";
}
设计便利性:
- 将 Provider 的差异(如:Mistral 要求工具 ID 必须是 9 位字母数字)封装在底层。
- 上层 Agent 无需编写
if (provider === 'mistral')这种带有“抽象泄漏”的代码。
2.4 成本与缓存感知的 Usage
export interface Usage {
input: number;
output: number;
cacheRead: number;
cacheWrite: number;
cost: { total: number; ... };
}
设计便利性:
- 原生支持 Prompt 缓存层级的计费统计。
- Agent 可以根据
cost反馈动态决定是否切换模型或执行上下文压缩。
3. republic - Tape-first 审计优先
核心设计理念
Tape-first LLM 客户端:所有交互都记录为结构化数据,支持完整的审计轨迹回放。
架构亮点
2.1 基于 any-llm 的统一执行层
class LLMCore:
"""Shared LLM execution utilities."""
def get_client(self, provider: str) -> AnyLLM:
"""基于配置缓存客户端实例."""
cache_key = self._freeze_cache_key(provider, api_key, api_base)
if cache_key not in self._client_cache:
self._client_cache[cache_key] = AnyLLM.create(...)
return self._client_cache[cache_key]
设计特点:
- 依赖 any-llm 库处理多 provider 细节
- 客户端实例缓存(基于配置 fingerprint)
- 统一的错误分类体系(ErrorKind)