跳转到内容

会话历史与上下文

Agent 既需要工作记录,也需要适合下一次模型调用的消息。Bub 用 Tape 保存记录,再从中选择条目构建模型上下文。这样可以保留历史,同时调整模型读取的内容。

core.Agent 和 BubAgent 共用这套机制,但默认存储和会话命名方式不同。

普通 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 会把同步存储适配为异步调用。持久化和跨进程协调由存储与应用负责。

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 前生成。剩余请求仍然太大时,重试也可能失败。

传给 agent.run() 的 state 是可变字典,由本轮的工具、提示词片段和事件处理器共用。持久化由你的应用负责:可以把业务数据放入自己的存储,也可以把选定事实写到 Tape,在需要时显式读回。

默认 TapeRecorder保存会话交互和部分生命周期记录。需要保留的其他变量、自定义事件或决策,由应用显式记录。

每次 agent.run() 都打开当前 Tape 的分支。关闭流时,分支会把新条目追加到父 Tape。异常退出或提前取消也会合并,因此失败的运行仍可能留下有用的部分历史。

传入 merge_back=False,可以在退出时丢弃分支记录。它只影响 Tape 写入。工具已经发送的请求、修改的文件等副作用仍然保留。

自己管理一段工作时,可以使用 agent.session(name, merge_back=...) 或 tape.fork_tape(merge_back=...)。两者默认都在退出时合并,包括异常退出。

如果同一 Tape 上的运行需要看到前一轮的完整结果,应让它们依次执行。会话锁由你的应用提供。FastAPI 示例展示单进程内的锁;多个 worker 需要跨进程协调。

TapeSidecar 用独立的 Tape 保存关联数据。挂载的 sidecar 随主 Tape 一起分支、合并和重置,其条目不进入主上下文。自己的应用需要这项能力时,可以通过 Agent(sidecars=...) 显式传入。

Bub 应用为大型工具结果挂载 spill sidecar。会话中保留有限预览和句柄,Agent 可以通过 spill.read 分段读取。合并分支或重置 Tape 时,sidecar 的失败会单独记录,主 Tape 操作仍可完成。应用的 spill 限制属于配置。

BubAgent 使用 BubFramework 提供的存储。内置应用使用 Bub home 下的 FuzzyFileTapeStore,通常位于 ~/.bub/tapes/。Tape 名称由工作区路径和会话 ID 的哈希组合而成,因此不同工作区中相同的会话 ID 拥有独立历史。

应用通过 provide_tape_store 替换存储,通过 build_tape_context 选择上下文。SQLAlchemy 指南展示这条插件路线。直接使用 SDK 的服务则按本页前面的方式,把实现传给 Agent(store=...)。

内置应用会从 Tape 记录恢复选定的会话设置。其他本轮状态需要单独实现持久化。