会话历史与上下文
Agent 既需要工作记录,也需要适合下一次模型调用的消息。Bub 用 Tape 保存记录,再从中选择条目构建模型上下文。这样可以保留历史,同时调整模型读取的内容。
core.Agent 和 BubAgent 共用这套机制,但默认存储和会话命名方式不同。
选择历史存储位置
Section titled “选择历史存储位置”普通 Agent 默认创建 InMemoryTapeStore,历史只在该存储实例存活期间保留。传入文件存储,可以在程序重启后继续使用:
from pathlib import Path
from bub.core import Agent
from bub.core.store import FileTapeStore
agent = Agent(store=FileTapeStore(Path("sessions")))
每次 agent.run(..., tape="customer-42") 都使用这个存储中的指定 Tape。复用名称可以继续会话;使用不同名称可以隔离会话。Agent 直接使用你传入的名称;应用层有自己的工作区与会话命名规则。
agent.tape(name) 可以在运行之外访问 Tape,写入会直接进入存储:
async def inspect_history() -> None:
tape = agent.tape("customer-42")
messages = await tape.read_messages()
entries = list(await tape.query().all())
print(len(messages), "model messages;", len(entries), "stored entries")
自定义存储需要实现 bub.core.store 中的 TapeStore 或 AsyncTapeStore。直接传给 Agent(store=my_store) 即可。
| 方法 | 职责 |
|---|---|
list_tapes() |
返回已保存的 Tape 名称。 |
fetch_all(query) |
按 TapeQuery 的过滤与顺序规则返回条目。 |
append(tape, entry) |
向指定 Tape 追加条目。 |
reset(tape) |
清空指定 Tape。 |
TapeStore 使用同步方法,AsyncTapeStore 使用异步方法。Agent 会把同步存储适配为异步调用。持久化和跨进程协调由存储与应用负责。
选择模型读取的内容
Section titled “选择模型读取的内容”Tape 条目包括消息、工具调用、工具结果、事件和锚点。正常记录会追加条目;需要清空历史时,可以显式调用 Tape.reset() 等操作。
默认 TapeContext 选择最近锚点之后的会话条目,并转换成 Republic 消息:
| 条目 | 模型上下文 |
|---|---|
anchor |
默认查询不包含锚点。自定义查询包含锚点时,默认选择器将其转换为 assistant 消息。 |
message |
保存的消息。 |
tool_call |
包含工具调用的 assistant 消息。 |
tool_result |
与前面的调用配对的工具消息。 |
| 其他事件 | 默认选择器忽略。 |
锚点标记上下文的新起点。handoff() 写入锚点,但保留此前的历史:
async def start_next_phase() -> None:
await agent.tape("customer-42").handoff(
name="order-reviewed",
state={"summary": "Order A123 has shipped; the customer needs tracking details."},
)
这里的 state 保存在锚点上,位于默认查询的起点之前。要在下一阶段继续使用摘要,需要将它放入下一次 prompt,或在自己的上下文策略中显式选择。将其中的值恢复到本轮可变状态字典,也需要自己编写加载逻辑。新会话会自动获得一个 session/start 锚点。
向 Agent 传入 context=TapeContext(...),可以选择其他锚点,或提供自己的 select(entries, context) 函数。选择器返回 Republic 消息,也支持异步结果。default_tape_context() 包含工具交互;未指定 select 的普通 TapeContext 使用更简单的消息转换。标记为 context=False 的条目会在默认或自定义选择器运行前排除。
遇到可识别的上下文长度错误时,Agent 会自动 handoff 一次,再用缩短后的上下文重试。需要保留前面历史的摘要时,应在 handoff 前生成。剩余请求仍然太大时,重试也可能失败。
决定哪些信息需要保留
Section titled “决定哪些信息需要保留”传给 agent.run() 的 state 是可变字典,由本轮的工具、提示词片段和事件处理器共用。持久化由你的应用负责:可以把业务数据放入自己的存储,也可以把选定事实写到 Tape,在需要时显式读回。
默认 TapeRecorder保存会话交互和部分生命周期记录。需要保留的其他变量、自定义事件或决策,由应用显式记录。
分支与部分结果
Section titled “分支与部分结果”每次 agent.run() 都打开当前 Tape 的分支。关闭流时,分支会把新条目追加到父 Tape。异常退出或提前取消也会合并,因此失败的运行仍可能留下有用的部分历史。
传入 merge_back=False,可以在退出时丢弃分支记录。它只影响 Tape 写入。工具已经发送的请求、修改的文件等副作用仍然保留。
自己管理一段工作时,可以使用 agent.session(name, merge_back=...) 或 tape.fork_tape(merge_back=...)。两者默认都在退出时合并,包括异常退出。
如果同一 Tape 上的运行需要看到前一轮的完整结果,应让它们依次执行。会话锁由你的应用提供。FastAPI 示例展示单进程内的锁;多个 worker 需要跨进程协调。
将大型结果放在会话旁边
Section titled “将大型结果放在会话旁边”TapeSidecar 用独立的 Tape 保存关联数据。挂载的 sidecar 随主 Tape 一起分支、合并和重置,其条目不进入主上下文。自己的应用需要这项能力时,可以通过 Agent(sidecars=...) 显式传入。
Bub 应用为大型工具结果挂载 spill sidecar。会话中保留有限预览和句柄,Agent 可以通过 spill.read 分段读取。合并分支或重置 Tape 时,sidecar 的失败会单独记录,主 Tape 操作仍可完成。应用的 spill 限制属于配置。
Bub 应用中的存储
Section titled “Bub 应用中的存储”BubAgent 使用 BubFramework 提供的存储。内置应用使用 Bub home 下的 FuzzyFileTapeStore,通常位于 ~/.bub/tapes/。Tape 名称由工作区路径和会话 ID 的哈希组合而成,因此不同工作区中相同的会话 ID 拥有独立历史。
应用通过 provide_tape_store 替换存储,通过 build_tape_context 选择上下文。SQLAlchemy 指南展示这条插件路线。直接使用 SDK 的服务则按本页前面的方式,把实现传给 Agent(store=...)。
内置应用会从 Tape 记录恢复选定的会话设置。其他本轮状态需要单独实现持久化。