feat(v2): T-M1 采纳 pi 会话树设计——AgentSession fork 分叉(parent_id/fork_point 血统 + 值拷贝隔离)
- SessionStore.fork(sid, turn_index, title):值拷贝 turns[:n+1] 生成新会话,
原会话不变;新会话记录 parent_id/fork_point(pi 的 id/parentId 树思想);
拷贝式而非共享存储引用——规避并发写冲突,会话体量小冗余可接受
- 旧会话数据无血统字段自然兼容(dict.get 语义,零迁移,对齐 pi 的版本迁移纪律)
- 新增 POST /agent/sessions/{sid}/fork 端点(404 = 会话不存在/分叉点越界)
- 新增 tests/test_session_fork.py 6 项(拷贝正确性/缺省语义/越界/深拷贝/
旧数据兼容/端点 200+404)
This commit is contained in:
@@ -15,6 +15,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import copy
|
||||
import json
|
||||
import time
|
||||
import uuid
|
||||
@@ -889,6 +890,36 @@ class SessionStore:
|
||||
self.save(sess)
|
||||
return sess
|
||||
|
||||
def fork(self, sid: str, turn_index: Optional[int] = None,
|
||||
title: str = "") -> Optional[AgentSession]:
|
||||
"""从既有会话分叉新会话(T-M1,采纳 pi 会话树 id/parentId 设计)。
|
||||
|
||||
值拷贝 turns[:turn_index+1](缺省 = 全部已完结轮次;-1 = 空会话壳),
|
||||
原会话不变;新会话记录 parent_id / fork_point 保留血统。采用拷贝式
|
||||
而非 pi 的共享存储引用——规避并发写冲突,代价是 fork 后磁盘冗余
|
||||
(会话体量小,可接受)。turn_index 越界返回 None。
|
||||
"""
|
||||
src = self.get(sid)
|
||||
if src is None:
|
||||
return None
|
||||
turns = src.data.get("turns", []) or []
|
||||
n = len(turns) - 1 if turn_index is None else int(turn_index)
|
||||
if n < -1 or n >= len(turns):
|
||||
return None
|
||||
data = copy.deepcopy(src.data)
|
||||
data["id"] = "as" + uuid.uuid4().hex[:10]
|
||||
base_title = (title or "").strip() or (src.data.get("title") or "会话") + "·分叉"
|
||||
data["title"] = base_title[:24]
|
||||
data["parent_id"] = sid
|
||||
data["fork_point"] = n
|
||||
data["created_at"] = time.time()
|
||||
data["busy"] = False
|
||||
data["turns"] = copy.deepcopy(turns[:n + 1]) if n >= 0 else []
|
||||
sess = AgentSession(data)
|
||||
self._cache[data["id"]] = sess
|
||||
self._save(sess)
|
||||
return sess
|
||||
|
||||
def save(self, sess: AgentSession) -> None:
|
||||
self._cache[sess.data["id"]] = sess
|
||||
self._save(sess)
|
||||
|
||||
Reference in New Issue
Block a user