会话与历史管理机制对比 - Tape vs 其他设计
Related topics: [[llm-framework-comparison]], [[llm-abstraction-comparison]]
Overview
本文对比分析五个框架的会话与历史管理机制,重点分析 Republic 的 Tape 设计与其他框架的差异。
1. Republic: Tape (磁带) 设计
核心概念
Tape 名字来源于 "磁带" (Magnetic Tape),象征着:
- 只追加 (Append-only) - 不能修改历史,只能添加新记录
- 顺序记录 - 按时间顺序记录所有交互
- 可回放 - 可以从头读取完整的对话历史
数据结构
# tape/entries.py
@dataclass(frozen=True)
class TapeEntry:
"""Tape 中的单个条目 - 不可变"""
id: int
kind: str # 条目类型
payload: dict # 具体内容
meta: dict # 元数据 (run_id, provider, model 等)
Entry 类型
| kind | 含义 | payload |
|---|---|---|
message | 用户/助手消息 | {"role": "...", "content": "..."} |
system | 系统提示 | {"content": "..."} |
tool_call | 工具调用 | {"calls": [...]} |
tool_result | 工具结果 | {"results": [...]} |
error | 错误记录 | ErrorPayload.as_dict() |
anchor | 锚点 | {"name": "...", "state": {...}} |
event | 事件记录 | {"name": "run", "data": {...}} |
Anchor (锚点) - 上下文窗口管理
# tape/context.py
@dataclass(frozen=True)
class TapeContext:
"""控制如何从 Tape 中选择消息"""
anchor: AnchorSelector = LAST_ANCHOR # 从哪个锚点开始
select: Callable | None = None # 自定义选择器
# Anchor 选择器:
# - LAST_ANCHOR: 从最近的锚点开始 (默认)
# - None: 使用完整的 Tape
# - "anchor_name": 从指定名称的锚点开始
工作流程
┌─────────────────────────────────────────────────────────────────┐
│ Tape (磁带) │
├─────────────────────────────────────────────────────────────────┤
│ [0] system: "You are a helpful assistant" │
│ [1] message: {"role": "user", "content": "Hello"} │
│ [2] message: {"role": "assistant", "content": "Hi!"} │
│ [3] anchor: {"name": "greeting_done", "state": {...}} ← 锚点1 │
│ [4] message: {"role": "user", "content": "What's weather?"} │
│ [5] tool_call: {"calls": [{"name": "get_weather", ...}]} │
│ [6] tool_result: {"results": ["sunny"]} │
│ [7] message: {"role": "assistant", "content": "It's sunny"} │
│ [8] anchor: {"name": "weather_done", "state": {...}} ← 锚点2 │
└─────────────────────────────────────────────────────────────────┘
↑
│ TapeContext(anchor=LAST_ANCHOR)
│ 只读取锚点2之后的消息发送给 LLM
↓
┌─────────────────────────────────────────────────────────────────┐
│ 发送给 LLM 的消息: │
│ [4] message: {"role": "user", "content": "What's weather?"} │
│ [5] tool_call / tool_result │
│ [7] message: {"role": "assistant", "content": "It's sunny"} │
│ + 新的用户消息 │
└─────────────────────────────────────────────────────────────────┘
使用示例
from republic import LLM
from republic.tape import TapeContext
llm = LLM(model="openai:gpt-4")
# 创建 Tape (命名会话)
tape = llm.tape("conversation-1")
# 第一次对话
tape.chat("Hello!")
# 创建锚点 - 标记检查点
tape.handoff("intro_done", state={"user_name": "Alice"})
# 第二次对话 - 默认只发送锚点之后的消息
tape.chat("What's the weather?")
# 读取完整历史
all_entries = tape.read_entries()
# 只读取锚点之后的消息 (用于发送给 LLM)
messages = tape.read_messages()
# 自定义上下文选择
context = TapeContext(anchor=None) # 使用完整历史
all_messages = tape.read_messages(context=context)
# 查询特定条目
tool_calls = tape.query().tool_calls().all()
errors = tape.query().errors().all()