workspace.py 2.5 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758
  1. """按服务端会话身份映射文件根,虚拟路径保持不变。"""
  2. import hashlib
  3. from pathlib import Path
  4. from typing import Any
  5. import config
  6. def session_root(user_name: str, session_id: str) -> Path:
  7. # 哈希避免用户名或外部 ID 成为路径片段;不迁移旧共享文件以免猜错归属。
  8. user_key = hashlib.sha256(user_name.encode()).hexdigest()
  9. session_key = hashlib.sha256(session_id.encode()).hexdigest()
  10. root = config.ROOT / "data" / "workspaces" / user_key / session_key
  11. root.mkdir(parents=True, exist_ok=True)
  12. return root
  13. def _session_identity(runtime) -> tuple[str, str]:
  14. """取服务端会话身份:先看显式传入的 ``runtime.config``,再回落到运行上下文。
  15. **两个来源都要试,不能只看第一个。** 摘要压缩中间件在模型调用里需要后端时按框架
  16. 自己的写法构造 runtime:
  17. config = cast("RunnableConfig", getattr(runtime, "config", {}))
  18. 而那里的 ``request.runtime`` 是 ``langgraph.runtime.Runtime``——**它没有 ``config``
  19. 字段**,于是 config 恒为 ``{}``(框架注释也明说空字典是合法取值)。若只判断"属性
  20. 是否存在",就会把 ``{}`` 当成有效配置、以"缺少会话身份"炸掉整轮——表现为长会话或
  21. 读了长文档的子智能体**一触发摘要压缩就失败**,而不是压缩生效。
  22. """
  23. from langgraph.config import get_config
  24. candidates: list[Any] = [getattr(runtime, "config", None)]
  25. try:
  26. candidates.append(get_config())
  27. except RuntimeError:
  28. # 不在 runnable 上下文里(脚本或测试直接调工厂)时没有运行配置可言
  29. pass
  30. for candidate in candidates:
  31. configurable = (candidate or {}).get("configurable") or {}
  32. user = configurable.get("workspace_user")
  33. session = configurable.get("workspace_session")
  34. if user and session:
  35. return str(user), str(session)
  36. raise ValueError("文件后端缺少服务端会话身份")
  37. def backend_for_runtime(runtime):
  38. from deepagents.backends import CompositeBackend, FilesystemBackend
  39. user, session = _session_identity(runtime)
  40. root = session_root(user, session)
  41. for directory in ("workspace", "conversation_history"):
  42. (root / directory).mkdir(exist_ok=True)
  43. return CompositeBackend(
  44. default=FilesystemBackend(root_dir=root, virtual_mode=True),
  45. routes={"/skills/": FilesystemBackend(root_dir=config.ROOT / "skills", virtual_mode=True)},
  46. )