wisruntime 0.4.2__tar.gz → 0.5.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. {wisruntime-0.4.2/wisruntime.egg-info → wisruntime-0.5.0}/PKG-INFO +57 -6
  2. {wisruntime-0.4.2 → wisruntime-0.5.0}/README.md +56 -5
  3. {wisruntime-0.4.2 → wisruntime-0.5.0}/pyproject.toml +1 -1
  4. {wisruntime-0.4.2 → wisruntime-0.5.0}/wisruntime/adapters/__init__.py +1 -0
  5. {wisruntime-0.4.2 → wisruntime-0.5.0}/wisruntime/adapters/langgraph/__init__.py +9 -0
  6. wisruntime-0.5.0/wisruntime/adapters/langgraph/_utils.py +34 -0
  7. wisruntime-0.5.0/wisruntime/adapters/langgraph/memory_middleware.py +223 -0
  8. {wisruntime-0.4.2 → wisruntime-0.5.0}/wisruntime/adapters/langgraph/wrap_agent.py +18 -0
  9. wisruntime-0.5.0/wisruntime/adapters/memory/__init__.py +35 -0
  10. wisruntime-0.5.0/wisruntime/memory/__init__.py +17 -0
  11. wisruntime-0.5.0/wisruntime/memory/client.py +108 -0
  12. wisruntime-0.5.0/wisruntime/memory/formatter.py +67 -0
  13. wisruntime-0.5.0/wisruntime/memory/model.py +45 -0
  14. {wisruntime-0.4.2 → wisruntime-0.5.0/wisruntime.egg-info}/PKG-INFO +57 -6
  15. {wisruntime-0.4.2 → wisruntime-0.5.0}/wisruntime.egg-info/SOURCES.txt +7 -0
  16. {wisruntime-0.4.2 → wisruntime-0.5.0}/MANIFEST.in +0 -0
  17. {wisruntime-0.4.2 → wisruntime-0.5.0}/setup.cfg +0 -0
  18. {wisruntime-0.4.2 → wisruntime-0.5.0}/wisruntime/__init__.py +0 -0
  19. {wisruntime-0.4.2 → wisruntime-0.5.0}/wisruntime/adapters/langgraph/builtin.py +0 -0
  20. {wisruntime-0.4.2 → wisruntime-0.5.0}/wisruntime/adapters/langgraph/converter.py +0 -0
  21. {wisruntime-0.4.2 → wisruntime-0.5.0}/wisruntime/adapters/langgraph/tool_adapter.py +0 -0
  22. {wisruntime-0.4.2 → wisruntime-0.5.0}/wisruntime/config.py +0 -0
  23. {wisruntime-0.4.2 → wisruntime-0.5.0}/wisruntime/conversation/__init__.py +0 -0
  24. {wisruntime-0.4.2 → wisruntime-0.5.0}/wisruntime/conversation/client.py +0 -0
  25. {wisruntime-0.4.2 → wisruntime-0.5.0}/wisruntime/conversation/model.py +0 -0
  26. {wisruntime-0.4.2 → wisruntime-0.5.0}/wisruntime/conversation/recorder.py +0 -0
  27. {wisruntime-0.4.2 → wisruntime-0.5.0}/wisruntime/exceptions.py +0 -0
  28. {wisruntime-0.4.2 → wisruntime-0.5.0}/wisruntime/model.py +0 -0
  29. {wisruntime-0.4.2 → wisruntime-0.5.0}/wisruntime/server/__init__.py +0 -0
  30. {wisruntime-0.4.2 → wisruntime-0.5.0}/wisruntime/server/dify_handler.py +0 -0
  31. {wisruntime-0.4.2 → wisruntime-0.5.0}/wisruntime/server/invoker.py +0 -0
  32. {wisruntime-0.4.2 → wisruntime-0.5.0}/wisruntime/server/protocol.py +0 -0
  33. {wisruntime-0.4.2 → wisruntime-0.5.0}/wisruntime/server/server.py +0 -0
  34. {wisruntime-0.4.2 → wisruntime-0.5.0}/wisruntime/skill/__init__.py +0 -0
  35. {wisruntime-0.4.2 → wisruntime-0.5.0}/wisruntime/skill/client.py +0 -0
  36. {wisruntime-0.4.2 → wisruntime-0.5.0}/wisruntime/skill/model.py +0 -0
  37. {wisruntime-0.4.2 → wisruntime-0.5.0}/wisruntime/tool/__init__.py +0 -0
  38. {wisruntime-0.4.2 → wisruntime-0.5.0}/wisruntime/tool/client.py +0 -0
  39. {wisruntime-0.4.2 → wisruntime-0.5.0}/wisruntime/tool/model.py +0 -0
  40. {wisruntime-0.4.2 → wisruntime-0.5.0}/wisruntime/tool/protocol.py +0 -0
  41. {wisruntime-0.4.2 → wisruntime-0.5.0}/wisruntime.egg-info/dependency_links.txt +0 -0
  42. {wisruntime-0.4.2 → wisruntime-0.5.0}/wisruntime.egg-info/requires.txt +0 -0
  43. {wisruntime-0.4.2 → wisruntime-0.5.0}/wisruntime.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wisruntime
3
- Version: 0.4.2
3
+ Version: 0.5.0
4
4
  Summary: 企业级 Agent Runtime SDK — 让 AI 工程师专注 Agent 能力开发,无需关心底层基础设施
5
5
  Author: WIS Team
6
6
  License-Expression: MIT
@@ -713,6 +713,30 @@ def create_server(
713
713
  └─────────────────────────────────────────────────────────────────────┘
714
714
 
715
715
  故事 6 ──────────────────────────────────────────────────────────────
716
+ │ 用户故事: 作为 AI 工程师,我可以用 WishubMemoryMiddleware 为我的 │
717
+ │ DeepAgents Agent 添加长期记忆能力,自动检索用户历史对话记忆 │
718
+ │ │
719
+ │ 验收标准: │
720
+ │ from deepagents import create_deep_agent │
721
+ │ from wisruntime.adapters.langgraph import WishubMemoryMiddleware │
722
+ │ from wisruntime.memory import WishubMemoryClient │
723
+ │ │
724
+ │ memory_client = WishubMemoryClient( │
725
+ │ base_url="https://wishub.example.com", │
726
+ │ api_key="your-api-key" │
727
+ │ ) │
728
+ │ middleware = WishubMemoryMiddleware( │
729
+ │ client=memory_client, │
730
+ │ namespaces=["user_profile", "preferences"], │
731
+ │ top_k=5 │
732
+ │ ) │
733
+ │ agent = create_deep_agent(model=model, middlewares=[middleware]) │
734
+ │ # → Agent 在每次对话前自动检索相关历史记忆 │
735
+ │ # → 记忆以结构化 XML 格式注入系统提示词 │
736
+ │ # → 支持优雅降级: API 超时/失败时返回空列表,不影响对话 │
737
+ └─────────────────────────────────────────────────────────────────────┘
738
+
739
+ 故事 7 ──────────────────────────────────────────────────────────────
716
740
  │ 用户故事: 作为 AI 工程师,我的 Agent 对话历史可以自动持久化到 │
717
741
  │ WIS Hub Checkpoint 服务,支持断点续传 │
718
742
  │ │
@@ -782,16 +806,36 @@ Phase 3: 平台能力接入 (MCP ✅ 已完成 — v0.3)
782
806
  分页发现 / 详情 / 302 预签名下载 / 严格 SemVer 版本, create_skill_sources() deepagents 集成
783
807
  □ SandboxClient + Sandbox 数据模型
784
808
 
809
+ Phase 3.5: 记忆能力接入 (✅ 已完成 — v0.4)
810
+ ═══════════════════════════════
811
+ 目标: 用户故事 6 — WishubMemoryMiddleware 长期记忆
812
+
813
+ ✅ memory/ 模块: MemoryItem, RetrieveRequest, RetrieveResponse 数据模型
814
+ ✅ WishubMemoryClient — 异步 HTTP 客户端,对接 Wishub Memory API
815
+ - 优雅降级: 超时/错误返回空列表,不影响对话
816
+ - 完整日志: debug/info/warning/error 四级
817
+ ✅ MemoryPromptFormatter — 记忆列表 → 结构化 XML 提示词片段
818
+ - 支持相关度评分显示
819
+ - 空记忆占位符
820
+ - 可选使用指南
821
+ ✅ WishubMemoryMiddleware — DeepAgents 中间件
822
+ - abefore_agent: 检索记忆并存入 state
823
+ - modify_request: 注入记忆到系统提示词
824
+ - 跳过重复检索
825
+ - user_id/query 缺失时优雅处理
826
+ ✅ wrap_agent config 注入 — user_id, thread_id 传递到 LangGraph config.configurable
827
+ ✅ 完整测试覆盖 (39 个测试用例全部通过)
828
+
785
829
  Phase 4: 高级特性
786
830
  ════════════════
787
- 目标: 用户故事 6 — Checkpoint、多协议
831
+ 目标: 用户故事 7 — Checkpoint、多协议
788
832
 
789
833
  □ CheckpointClient + 自动持久化
790
834
  □ AGUIHandler
791
835
  □ Stop endpoint
792
836
  □ ApiKeyMiddleware
793
837
 
794
- Phase 4: 多框架 + 多协议
838
+ Phase 5: 多框架 + 多协议
795
839
  ══════════════════════
796
840
  目标: 验证可扩展性
797
841
 
@@ -817,12 +861,19 @@ wisruntime/
817
861
  │ └── client.py # MCPClient(ToolClient) — 薄封装标准 mcp 包
818
862
  │
819
863
  ├── adapters/ # Adapter + Capability Bridge
864
+ │ ├── memory/ # Memory 记忆检索适配器 (✅ v0.4)
865
+ │ │ ├── __init__.py # 导出 WishubMemoryClient, MemoryPromptFormatter, MemoryItem
866
+ │ │ ├── model.py # MemoryItem, RetrieveRequest, RetrieveResponse (Pydantic 模型)
867
+ │ │ ├── client.py # WishubMemoryClient — 异步 HTTP 客户端,优雅降级
868
+ │ │ └── formatter.py # MemoryPromptFormatter — 记忆列表 → XML 提示词片段
869
+ │ │
820
870
  │ └── langgraph/ # LangGraph/LangChain/DeepAgents 框架 (✅ v0.3)
821
- │ ├── __init__.py # 导出 LangGraphEventConverter, wrap_agent, LangChainToolAdapter, create_model, create_mcp_tools
871
+ │ ├── __init__.py # 导出 LangGraphEventConverter, wrap_agent, LangChainToolAdapter, create_model, create_mcp_tools, WishubMemoryMiddleware
822
872
  │ ├── converter.py # LangGraphEventConverter (astream_events → CanonicalEvent)
823
- │ ├── wrap_agent.py # wrap_agent() 糖函数
873
+ │ ├── wrap_agent.py # wrap_agent() 糖函数 (注入 config: user_id, thread_id)
824
874
  │ ├── tool_adapter.py # LangChainToolAdapter (ToolInfo → StructuredTool)
825
- │ └── builtin.py # create_model() / create_mcp_tools() 一行接入
875
+ │ ├── builtin.py # create_model() / create_mcp_tools() 一行接入
876
+ │ └── memory_middleware.py # WishubMemoryMiddleware — DeepAgents 记忆中间件
826
877
  │
827
878
  ├── server/ # Server 层
828
879
  │ ├── __init__.py
@@ -681,6 +681,30 @@ def create_server(
681
681
  └─────────────────────────────────────────────────────────────────────┘
682
682
 
683
683
  故事 6 ──────────────────────────────────────────────────────────────
684
+ │ 用户故事: 作为 AI 工程师,我可以用 WishubMemoryMiddleware 为我的 │
685
+ │ DeepAgents Agent 添加长期记忆能力,自动检索用户历史对话记忆 │
686
+ │ │
687
+ │ 验收标准: │
688
+ │ from deepagents import create_deep_agent │
689
+ │ from wisruntime.adapters.langgraph import WishubMemoryMiddleware │
690
+ │ from wisruntime.memory import WishubMemoryClient │
691
+ │ │
692
+ │ memory_client = WishubMemoryClient( │
693
+ │ base_url="https://wishub.example.com", │
694
+ │ api_key="your-api-key" │
695
+ │ ) │
696
+ │ middleware = WishubMemoryMiddleware( │
697
+ │ client=memory_client, │
698
+ │ namespaces=["user_profile", "preferences"], │
699
+ │ top_k=5 │
700
+ │ ) │
701
+ │ agent = create_deep_agent(model=model, middlewares=[middleware]) │
702
+ │ # → Agent 在每次对话前自动检索相关历史记忆 │
703
+ │ # → 记忆以结构化 XML 格式注入系统提示词 │
704
+ │ # → 支持优雅降级: API 超时/失败时返回空列表,不影响对话 │
705
+ └─────────────────────────────────────────────────────────────────────┘
706
+
707
+ 故事 7 ──────────────────────────────────────────────────────────────
684
708
  │ 用户故事: 作为 AI 工程师,我的 Agent 对话历史可以自动持久化到 │
685
709
  │ WIS Hub Checkpoint 服务,支持断点续传 │
686
710
  │ │
@@ -750,16 +774,36 @@ Phase 3: 平台能力接入 (MCP ✅ 已完成 — v0.3)
750
774
  分页发现 / 详情 / 302 预签名下载 / 严格 SemVer 版本, create_skill_sources() deepagents 集成
751
775
  □ SandboxClient + Sandbox 数据模型
752
776
 
777
+ Phase 3.5: 记忆能力接入 (✅ 已完成 — v0.4)
778
+ ═══════════════════════════════
779
+ 目标: 用户故事 6 — WishubMemoryMiddleware 长期记忆
780
+
781
+ ✅ memory/ 模块: MemoryItem, RetrieveRequest, RetrieveResponse 数据模型
782
+ ✅ WishubMemoryClient — 异步 HTTP 客户端,对接 Wishub Memory API
783
+ - 优雅降级: 超时/错误返回空列表,不影响对话
784
+ - 完整日志: debug/info/warning/error 四级
785
+ ✅ MemoryPromptFormatter — 记忆列表 → 结构化 XML 提示词片段
786
+ - 支持相关度评分显示
787
+ - 空记忆占位符
788
+ - 可选使用指南
789
+ ✅ WishubMemoryMiddleware — DeepAgents 中间件
790
+ - abefore_agent: 检索记忆并存入 state
791
+ - modify_request: 注入记忆到系统提示词
792
+ - 跳过重复检索
793
+ - user_id/query 缺失时优雅处理
794
+ ✅ wrap_agent config 注入 — user_id, thread_id 传递到 LangGraph config.configurable
795
+ ✅ 完整测试覆盖 (39 个测试用例全部通过)
796
+
753
797
  Phase 4: 高级特性
754
798
  ════════════════
755
- 目标: 用户故事 6 — Checkpoint、多协议
799
+ 目标: 用户故事 7 — Checkpoint、多协议
756
800
 
757
801
  □ CheckpointClient + 自动持久化
758
802
  □ AGUIHandler
759
803
  □ Stop endpoint
760
804
  □ ApiKeyMiddleware
761
805
 
762
- Phase 4: 多框架 + 多协议
806
+ Phase 5: 多框架 + 多协议
763
807
  ══════════════════════
764
808
  目标: 验证可扩展性
765
809
 
@@ -785,12 +829,19 @@ wisruntime/
785
829
  │ └── client.py # MCPClient(ToolClient) — 薄封装标准 mcp 包
786
830
  │
787
831
  ├── adapters/ # Adapter + Capability Bridge
832
+ │ ├── memory/ # Memory 记忆检索适配器 (✅ v0.4)
833
+ │ │ ├── __init__.py # 导出 WishubMemoryClient, MemoryPromptFormatter, MemoryItem
834
+ │ │ ├── model.py # MemoryItem, RetrieveRequest, RetrieveResponse (Pydantic 模型)
835
+ │ │ ├── client.py # WishubMemoryClient — 异步 HTTP 客户端,优雅降级
836
+ │ │ └── formatter.py # MemoryPromptFormatter — 记忆列表 → XML 提示词片段
837
+ │ │
788
838
  │ └── langgraph/ # LangGraph/LangChain/DeepAgents 框架 (✅ v0.3)
789
- │ ├── __init__.py # 导出 LangGraphEventConverter, wrap_agent, LangChainToolAdapter, create_model, create_mcp_tools
839
+ │ ├── __init__.py # 导出 LangGraphEventConverter, wrap_agent, LangChainToolAdapter, create_model, create_mcp_tools, WishubMemoryMiddleware
790
840
  │ ├── converter.py # LangGraphEventConverter (astream_events → CanonicalEvent)
791
- │ ├── wrap_agent.py # wrap_agent() 糖函数
841
+ │ ├── wrap_agent.py # wrap_agent() 糖函数 (注入 config: user_id, thread_id)
792
842
  │ ├── tool_adapter.py # LangChainToolAdapter (ToolInfo → StructuredTool)
793
- │ └── builtin.py # create_model() / create_mcp_tools() 一行接入
843
+ │ ├── builtin.py # create_model() / create_mcp_tools() 一行接入
844
+ │ └── memory_middleware.py # WishubMemoryMiddleware — DeepAgents 记忆中间件
794
845
  │
795
846
  ├── server/ # Server 层
796
847
  │ ├── __init__.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "wisruntime"
7
- version = "0.4.2"
7
+ version = "0.5.0"
8
8
  description = "企业级 Agent Runtime SDK — 让 AI 工程师专注 Agent 能力开发,无需关心底层基础设施"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -2,6 +2,7 @@
2
2
 
3
3
  按框架分子目录组织:
4
4
  - langgraph/ — LangGraph/LangChain/DeepAgents 框架适配
5
+ - memory/ — DEPRECATED: 已移至 wisruntime.memory(此处仅为兼容性 shim)
5
6
  - (未来) llamaindex/ — LlamaIndex 框架适配
6
7
  - (未来) crewai/ — CrewAI 框架适配
7
8
 
@@ -6,10 +6,16 @@
6
6
  - LangChainToolAdapter: ToolClient → StructuredTool 适配器
7
7
  - create_model: 快速创建 LangChain 模型实例
8
8
  - create_mcp_tools: 一行获取 MCP 工具列表
9
+ - WishubMemoryMiddleware: Wishub 记忆检索中间件
9
10
  """
10
11
 
11
12
  from wisruntime.adapters.langgraph.builtin import create_mcp_tools, create_model
12
13
  from wisruntime.adapters.langgraph.converter import LangGraphEventConverter
14
+ from wisruntime.adapters.langgraph.memory_middleware import (
15
+ WishubMemoryMiddleware,
16
+ WishubMemoryState,
17
+ WishubMemoryStateUpdate,
18
+ )
13
19
  from wisruntime.adapters.langgraph.tool_adapter import LangChainToolAdapter
14
20
  from wisruntime.adapters.langgraph.wrap_agent import wrap_agent
15
21
 
@@ -19,4 +25,7 @@ __all__ = [
19
25
  "LangChainToolAdapter",
20
26
  "create_model",
21
27
  "create_mcp_tools",
28
+ "WishubMemoryMiddleware",
29
+ "WishubMemoryState",
30
+ "WishubMemoryStateUpdate",
22
31
  ]
@@ -0,0 +1,34 @@
1
+ """Utility functions for LangGraph middleware."""
2
+
3
+ from typing import Optional
4
+
5
+ from langchain_core.messages import SystemMessage
6
+
7
+
8
+ def append_to_system_message(
9
+ system_message: Optional[SystemMessage], content: str
10
+ ) -> SystemMessage:
11
+ """Append content to an existing system message or create a new one.
12
+
13
+ Args:
14
+ system_message: Existing system message or None
15
+ content: Content to append
16
+
17
+ Returns:
18
+ SystemMessage with appended content
19
+ """
20
+ if system_message is None:
21
+ return SystemMessage(content=content)
22
+
23
+ # Get existing content
24
+ existing_content = system_message.content
25
+ if isinstance(existing_content, str):
26
+ new_content = existing_content + "\n\n" + content
27
+ elif isinstance(existing_content, list):
28
+ # Handle list of content blocks
29
+ new_content = existing_content + [{"type": "text", "text": content}]
30
+ else:
31
+ new_content = str(existing_content) + "\n\n" + content
32
+
33
+ # Create new message with combined content
34
+ return SystemMessage(content=new_content)
@@ -0,0 +1,223 @@
1
+ """Wishub Memory Middleware for DeepAgents。"""
2
+
3
+ import logging
4
+ from typing import TYPE_CHECKING, Annotated, NotRequired, TypedDict, Optional
5
+
6
+ if TYPE_CHECKING:
7
+ from collections.abc import Awaitable, Callable
8
+ from langchain_core.runnables import RunnableConfig
9
+ from langgraph.runtime import Runtime
10
+
11
+ from langchain.agents.middleware.types import (
12
+ AgentMiddleware,
13
+ AgentState,
14
+ ContextT,
15
+ ModelRequest,
16
+ ModelResponse,
17
+ PrivateStateAttr,
18
+ ResponseT,
19
+ TracePolicy,
20
+ omit_payload,
21
+ )
22
+
23
+ from wisruntime.memory import WishubMemoryClient, MemoryPromptFormatter, MemoryItem
24
+ from wisruntime.adapters.langgraph._utils import append_to_system_message
25
+
26
+ logger = logging.getLogger(__name__)
27
+
28
+
29
+ class WishubMemoryState(AgentState):
30
+ """Wishub Memory Middleware 的状态 schema。"""
31
+
32
+ retrieved_memories: NotRequired[Annotated[list[MemoryItem], PrivateStateAttr]]
33
+
34
+
35
+ class WishubMemoryStateUpdate(TypedDict):
36
+ """状态更新。"""
37
+
38
+ retrieved_memories: list[MemoryItem]
39
+
40
+
41
+ class WishubMemoryMiddleware(AgentMiddleware[WishubMemoryState, ContextT, ResponseT]):
42
+ """Wishub 记忆检索中间件(DeepAgents)。
43
+
44
+ 在 Agent 执行前,根据用户查询检索相关记忆,并注入到系统提示词中。
45
+
46
+ Example:
47
+ >>> from wisruntime.adapters.langgraph import WishubMemoryMiddleware
48
+ >>> from deepagents import create_deep_agent
49
+ >>>
50
+ >>> middleware = WishubMemoryMiddleware(
51
+ ... base_url="https://aip-open-api-dev.infinitus.com.cn",
52
+ ... api_key="your-api-key",
53
+ ... namespaces=["preference", "history"],
54
+ ... top_k=5,
55
+ ... )
56
+ >>>
57
+ >>> agent = create_deep_agent(
58
+ ... model=model,
59
+ ... middleware=[middleware],
60
+ ... )
61
+ """
62
+
63
+ trace_policy = TracePolicy(process_inputs=omit_payload)
64
+ state_schema = WishubMemoryState
65
+
66
+ def __init__(
67
+ self,
68
+ *,
69
+ base_url: str,
70
+ api_key: str,
71
+ namespaces: Optional[list[str]] = None,
72
+ top_k: int = 5,
73
+ timeout: float = 5.0,
74
+ include_guidelines: bool = True,
75
+ ):
76
+ """初始化 Wishub Memory 中间件。
77
+
78
+ Args:
79
+ base_url: Wishub Memory API 基础地址
80
+ api_key: API Key (智能体通道鉴权)
81
+ namespaces: 可选的命名空间过滤列表
82
+ top_k: 检索记忆数量上限(默认5)
83
+ timeout: API 请求超时时间(秒)
84
+ include_guidelines: 是否在提示词中包含使用指南
85
+ """
86
+ self.client = WishubMemoryClient(
87
+ base_url=base_url,
88
+ api_key=api_key,
89
+ timeout=timeout,
90
+ )
91
+ self.namespaces = namespaces
92
+ self.top_k = top_k
93
+ self.include_guidelines = include_guidelines
94
+ self.formatter = MemoryPromptFormatter()
95
+
96
+ def _extract_user_query(self, state: WishubMemoryState) -> Optional[str]:
97
+ """从状态中提取用户最后一条消息作为检索查询。
98
+
99
+ Args:
100
+ state: Agent 状态
101
+
102
+ Returns:
103
+ 用户查询文本,如果没有则返回 None
104
+ """
105
+ messages = state.get("messages", [])
106
+
107
+ # 从后往前找第一条用户消息
108
+ for msg in reversed(messages):
109
+ # LangChain 消息对象
110
+ if hasattr(msg, "type") and msg.type == "human":
111
+ content = msg.content
112
+ if isinstance(content, str):
113
+ return content.strip()
114
+ # 字典格式消息
115
+ elif isinstance(msg, dict) and msg.get("role") == "user":
116
+ content = msg.get("content", "")
117
+ if isinstance(content, str):
118
+ return content.strip()
119
+
120
+ return None
121
+
122
+ def _get_user_id(self, state: WishubMemoryState, config: "RunnableConfig") -> Optional[str]:
123
+ """获取 user_id,支持多种来源(按优先级)。
124
+
125
+ 优先级:
126
+ 1. config.configurable.user_id(标准方式,来自 wrap_agent)
127
+ 2. state.user_id(兼容方案,如果有人直接在 state 里传)
128
+ 3. None(无法获取,跳过检索)
129
+
130
+ Args:
131
+ state: Agent 状态
132
+ config: Runnable 配置
133
+
134
+ Returns:
135
+ user_id 或 None
136
+ """
137
+ # 方案1:从 config 读取(推荐)
138
+ user_id = config.get("configurable", {}).get("user_id")
139
+ if user_id:
140
+ return user_id
141
+
142
+ # 方案2:从 state 读取(兼容)
143
+ user_id = state.get("user_id")
144
+ if user_id:
145
+ logger.debug("user_id found in state (fallback), consider using config.configurable")
146
+ return user_id
147
+
148
+ # 方案3:无法获取
149
+ return None
150
+
151
+ async def abefore_agent(
152
+ self,
153
+ state: WishubMemoryState,
154
+ runtime: "Runtime",
155
+ config: "RunnableConfig",
156
+ ) -> Optional[WishubMemoryStateUpdate]:
157
+ """在 Agent 执行前检索记忆。"""
158
+
159
+ # 跳过重复检索
160
+ if "retrieved_memories" in state:
161
+ logger.debug("Memories already retrieved, skipping")
162
+ return None
163
+
164
+ # 提取用户查询
165
+ query = self._extract_user_query(state)
166
+ if not query:
167
+ logger.debug("No user query found, skipping memory retrieval")
168
+ return WishubMemoryStateUpdate(retrieved_memories=[])
169
+
170
+ # 提取 userId
171
+ user_id = self._get_user_id(state, config)
172
+ if not user_id:
173
+ logger.warning(
174
+ "Cannot retrieve memories: user_id not found. "
175
+ "Check that wrap_agent is passing request.user to config.configurable"
176
+ )
177
+ return WishubMemoryStateUpdate(retrieved_memories=[])
178
+
179
+ # 调用检索接口
180
+ logger.debug(f"Retrieving memories for user={user_id}, query_len={len(query)}")
181
+ memories = await self.client.retrieve_memories(
182
+ query=query,
183
+ user_id=user_id,
184
+ namespaces=self.namespaces,
185
+ top_k=self.top_k,
186
+ )
187
+
188
+ logger.info(f"Retrieved {len(memories)} memories for user {user_id}")
189
+ return WishubMemoryStateUpdate(retrieved_memories=memories)
190
+
191
+ def modify_request(
192
+ self,
193
+ request: ModelRequest[ContextT],
194
+ ) -> ModelRequest[ContextT]:
195
+ """将检索到的记忆注入系统提示词。"""
196
+
197
+ memories = request.state.get("retrieved_memories", [])
198
+
199
+ # 格式化记忆提示词
200
+ memory_prompt = self.formatter.format_memories_prompt(
201
+ memories,
202
+ include_guidelines=self.include_guidelines,
203
+ )
204
+
205
+ # 注入到系统消息
206
+ new_system_message = append_to_system_message(
207
+ request.system_message,
208
+ memory_prompt,
209
+ )
210
+
211
+ if new_system_message is request.system_message:
212
+ return request
213
+
214
+ return request.override(system_message=new_system_message)
215
+
216
+ async def awrap_model_call(
217
+ self,
218
+ request: ModelRequest[ContextT],
219
+ handler: "Callable[[ModelRequest[ContextT]], Awaitable[ModelResponse[ResponseT]]]",
220
+ ) -> ModelResponse[ResponseT]:
221
+ """异步包装模型调用。"""
222
+ modified_request = self.modify_request(request)
223
+ return await handler(modified_request)
@@ -70,8 +70,17 @@ def _build_astream_events_invoker(agent_graph):
70
70
  messages = _agent_request_to_messages(request)
71
71
  converter = LangGraphEventConverter()
72
72
 
73
+ # 构建 config,将 AgentRequest 元信息注入 configurable
74
+ config = {
75
+ "configurable": {
76
+ "thread_id": request.conversation_id or "default",
77
+ "user_id": request.user_id,
78
+ }
79
+ }
80
+
73
81
  async for event in agent_graph.astream_events(
74
82
  {"messages": messages},
83
+ config=config,
75
84
  version="v2",
76
85
  ):
77
86
  for item in converter.convert(event):
@@ -87,9 +96,18 @@ def _build_astream_invoker(agent_graph, stream_mode: str):
87
96
  messages = _agent_request_to_messages(request)
88
97
  converter = LangGraphEventConverter()
89
98
 
99
+ # 构建 config,将 AgentRequest 元信息注入 configurable
100
+ config = {
101
+ "configurable": {
102
+ "thread_id": request.conversation_id or "default",
103
+ "user_id": request.user_id,
104
+ }
105
+ }
106
+
90
107
  async for event in agent_graph.astream(
91
108
  {"messages": messages},
92
109
  stream_mode=stream_mode,
110
+ config=config,
93
111
  ):
94
112
  for item in converter.convert(event):
95
113
  yield item
@@ -0,0 +1,35 @@
1
+ """
2
+ Deprecated: This module has moved to wisruntime.memory
3
+
4
+ This compatibility shim will be removed in a future version.
5
+ Please update your imports:
6
+ from wisruntime.adapters.memory import X # Old (deprecated)
7
+ from wisruntime.memory import X # New
8
+ """
9
+
10
+ import warnings
11
+
12
+ # Issue deprecation warning
13
+ warnings.warn(
14
+ "wisruntime.adapters.memory is deprecated and will be removed in a future version. "
15
+ "Please use wisruntime.memory instead.",
16
+ DeprecationWarning,
17
+ stacklevel=2,
18
+ )
19
+
20
+ # Re-export from new location for backward compatibility
21
+ from wisruntime.memory import (
22
+ WishubMemoryClient,
23
+ MemoryPromptFormatter,
24
+ MemoryItem,
25
+ RetrieveRequest,
26
+ RetrieveResponse,
27
+ )
28
+
29
+ __all__ = [
30
+ "WishubMemoryClient",
31
+ "MemoryPromptFormatter",
32
+ "MemoryItem",
33
+ "RetrieveRequest",
34
+ "RetrieveResponse",
35
+ ]
@@ -0,0 +1,17 @@
1
+ """
2
+ Wisruntime Memory Module
3
+
4
+ Provides client and utilities for interacting with Wishub Memory API.
5
+ """
6
+
7
+ from .client import WishubMemoryClient
8
+ from .formatter import MemoryPromptFormatter
9
+ from .model import MemoryItem, RetrieveRequest, RetrieveResponse
10
+
11
+ __all__ = [
12
+ "WishubMemoryClient",
13
+ "MemoryPromptFormatter",
14
+ "MemoryItem",
15
+ "RetrieveRequest",
16
+ "RetrieveResponse",
17
+ ]
@@ -0,0 +1,108 @@
1
+ """Wishub Memory API 客户端(框架无关)。"""
2
+
3
+ import logging
4
+ from typing import Optional
5
+
6
+ import httpx
7
+
8
+ from wisruntime.memory.model import (
9
+ MemoryItem,
10
+ RetrieveResponse,
11
+ )
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ class WishubMemoryClient:
17
+ """Wishub 记忆检索客户端。"""
18
+
19
+ def __init__(
20
+ self,
21
+ *,
22
+ base_url: str,
23
+ api_key: str,
24
+ timeout: float = 5.0,
25
+ ):
26
+ """初始化记忆客户端。
27
+
28
+ Args:
29
+ base_url: API 基础地址,如 "https://aip-open-api-dev.infinitus.com.cn"
30
+ api_key: API Key (智能体通道鉴权)
31
+ timeout: 请求超时时间(秒)
32
+ """
33
+ self.base_url = base_url.rstrip("/")
34
+ self.api_key = api_key
35
+ self.timeout = timeout
36
+ self._endpoint = f"{self.base_url}/wishub-memory/openapi/v1/memories/retrieve"
37
+
38
+ async def retrieve_memories(
39
+ self,
40
+ *,
41
+ query: str,
42
+ user_id: str,
43
+ namespaces: Optional[list[str]] = None,
44
+ top_k: int = 5,
45
+ ) -> list[MemoryItem]:
46
+ """检索相关记忆。
47
+
48
+ Args:
49
+ query: 用户查询文本(用于语义检索)
50
+ user_id: 用户ID(智能体通道必填)
51
+ namespaces: 可选的命名空间过滤
52
+ top_k: 返回记忆数量上限(默认5,最大20)
53
+
54
+ Returns:
55
+ 记忆列表。降级或失败时返回空列表(不抛异常)
56
+ """
57
+ headers = {
58
+ "Authorization": f"Bearer {self.api_key}",
59
+ "Content-Type": "application/json",
60
+ }
61
+
62
+ payload = {
63
+ "query": query,
64
+ "userId": user_id,
65
+ "topK": min(top_k, 20),
66
+ }
67
+
68
+ if namespaces:
69
+ payload["namespaces"] = namespaces
70
+
71
+ try:
72
+ async with httpx.AsyncClient(timeout=self.timeout) as client:
73
+ response = await client.post(
74
+ self._endpoint,
75
+ json=payload,
76
+ headers=headers,
77
+ )
78
+
79
+ logger.debug(
80
+ f"Memory retrieval: user={user_id}, query_len={len(query)}, "
81
+ f"status={response.status_code}"
82
+ )
83
+
84
+ if response.status_code != 200:
85
+ logger.warning(
86
+ f"Memory API returned {response.status_code}: {response.text[:200]}"
87
+ )
88
+ return []
89
+
90
+ result = RetrieveResponse(**response.json())
91
+
92
+ if not result.success:
93
+ logger.warning(f"Memory retrieval failed: {result.code} - {result.msg}")
94
+ return []
95
+
96
+ if result.data.degraded:
97
+ logger.warning("Memory service degraded, returning empty list")
98
+ return []
99
+
100
+ logger.info(f"Retrieved {len(result.data.memories)} memories for user {user_id}")
101
+ return result.data.memories
102
+
103
+ except httpx.TimeoutException:
104
+ logger.warning(f"Memory retrieval timeout after {self.timeout}s")
105
+ return []
106
+ except Exception as e:
107
+ logger.error(f"Memory retrieval error: {e}", exc_info=True)
108
+ return []
@@ -0,0 +1,67 @@
1
+ """记忆提示词格式化器(框架无关)。"""
2
+
3
+ from wisruntime.memory.model import MemoryItem
4
+
5
+
6
+ class MemoryPromptFormatter:
7
+ """将检索到的记忆格式化为提示词片段。"""
8
+
9
+ @staticmethod
10
+ def format_memories_prompt(
11
+ memories: list[MemoryItem],
12
+ *,
13
+ include_guidelines: bool = True,
14
+ ) -> str:
15
+ """格式化记忆为系统提示词片段。
16
+
17
+ Args:
18
+ memories: 记忆列表
19
+ include_guidelines: 是否包含使用指南
20
+
21
+ Returns:
22
+ 格式化的 XML 提示词片段
23
+ """
24
+ if not memories:
25
+ return MemoryPromptFormatter._empty_memory_prompt(include_guidelines)
26
+
27
+ # 构建记忆内容
28
+ memory_items = []
29
+ for i, mem in enumerate(memories, 1):
30
+ memory_items.append(
31
+ f"记忆 {i} (相关度: {mem.score:.2f}):\n{mem.content.strip()}"
32
+ )
33
+
34
+ memories_text = "\n\n".join(memory_items)
35
+
36
+ prompt = f"""<user_memory>
37
+ 以下是根据用户问题检索到的相关记忆:
38
+
39
+ {memories_text}
40
+ </user_memory>"""
41
+
42
+ if include_guidelines:
43
+ prompt += """
44
+
45
+ <memory_usage_guidelines>
46
+ - 上述记忆是从历史对话中检索出的相关信息
47
+ - 相关度分数仅供参考,不同记忆引擎的分数体系不可比
48
+ - 请结合当前问题上下文合理使用这些记忆
49
+ - 如果记忆内容与用户当前问题不符,请以用户当前的明确表述为准
50
+ - 记忆可能包含过时信息,请在回答时注意时效性
51
+ </memory_usage_guidelines>"""
52
+
53
+ return prompt
54
+
55
+ @staticmethod
56
+ def _empty_memory_prompt(include_guidelines: bool) -> str:
57
+ """无记忆时的占位提示。"""
58
+ prompt = "<user_memory>\n(未检索到相关历史记忆)\n</user_memory>"
59
+
60
+ if include_guidelines:
61
+ prompt += """
62
+
63
+ <memory_usage_guidelines>
64
+ - 当前未检索到相关历史记忆,请基于用户当前问题直接回答
65
+ </memory_usage_guidelines>"""
66
+
67
+ return prompt
@@ -0,0 +1,45 @@
1
+ """记忆检索数据模型(框架无关)。"""
2
+
3
+ from typing import Optional
4
+
5
+ from pydantic import BaseModel, ConfigDict, Field
6
+
7
+
8
+ class MemoryItem(BaseModel):
9
+ """单条记忆项。"""
10
+
11
+ model_config = ConfigDict(populate_by_name=True)
12
+
13
+ memory_id: str = Field(alias="memoryId")
14
+ content: str
15
+ score: float
16
+ metadata: Optional[dict] = None
17
+ user_id: str = Field(alias="userId")
18
+ app_code: Optional[str] = Field(None, alias="appCode")
19
+ run_id: Optional[str] = Field(None, alias="runId")
20
+ created_at: str = Field(alias="createdAt")
21
+
22
+
23
+ class RetrieveRequest(BaseModel):
24
+ """记忆检索请求。"""
25
+
26
+ query: str
27
+ user_id: str = Field(alias="userId")
28
+ namespaces: Optional[list[str]] = None
29
+ top_k: int = Field(5, alias="topK")
30
+
31
+
32
+ class RetrieveResponseData(BaseModel):
33
+ """检索响应数据。"""
34
+
35
+ memories: list[MemoryItem]
36
+ degraded: bool = False
37
+
38
+
39
+ class RetrieveResponse(BaseModel):
40
+ """记忆检索响应。"""
41
+
42
+ success: bool
43
+ code: str
44
+ msg: str
45
+ data: RetrieveResponseData
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wisruntime
3
- Version: 0.4.2
3
+ Version: 0.5.0
4
4
  Summary: 企业级 Agent Runtime SDK — 让 AI 工程师专注 Agent 能力开发,无需关心底层基础设施
5
5
  Author: WIS Team
6
6
  License-Expression: MIT
@@ -713,6 +713,30 @@ def create_server(
713
713
  └─────────────────────────────────────────────────────────────────────┘
714
714
 
715
715
  故事 6 ──────────────────────────────────────────────────────────────
716
+ │ 用户故事: 作为 AI 工程师,我可以用 WishubMemoryMiddleware 为我的 │
717
+ │ DeepAgents Agent 添加长期记忆能力,自动检索用户历史对话记忆 │
718
+ │ │
719
+ │ 验收标准: │
720
+ │ from deepagents import create_deep_agent │
721
+ │ from wisruntime.adapters.langgraph import WishubMemoryMiddleware │
722
+ │ from wisruntime.memory import WishubMemoryClient │
723
+ │ │
724
+ │ memory_client = WishubMemoryClient( │
725
+ │ base_url="https://wishub.example.com", │
726
+ │ api_key="your-api-key" │
727
+ │ ) │
728
+ │ middleware = WishubMemoryMiddleware( │
729
+ │ client=memory_client, │
730
+ │ namespaces=["user_profile", "preferences"], │
731
+ │ top_k=5 │
732
+ │ ) │
733
+ │ agent = create_deep_agent(model=model, middlewares=[middleware]) │
734
+ │ # → Agent 在每次对话前自动检索相关历史记忆 │
735
+ │ # → 记忆以结构化 XML 格式注入系统提示词 │
736
+ │ # → 支持优雅降级: API 超时/失败时返回空列表,不影响对话 │
737
+ └─────────────────────────────────────────────────────────────────────┘
738
+
739
+ 故事 7 ──────────────────────────────────────────────────────────────
716
740
  │ 用户故事: 作为 AI 工程师,我的 Agent 对话历史可以自动持久化到 │
717
741
  │ WIS Hub Checkpoint 服务,支持断点续传 │
718
742
  │ │
@@ -782,16 +806,36 @@ Phase 3: 平台能力接入 (MCP ✅ 已完成 — v0.3)
782
806
  分页发现 / 详情 / 302 预签名下载 / 严格 SemVer 版本, create_skill_sources() deepagents 集成
783
807
  □ SandboxClient + Sandbox 数据模型
784
808
 
809
+ Phase 3.5: 记忆能力接入 (✅ 已完成 — v0.4)
810
+ ═══════════════════════════════
811
+ 目标: 用户故事 6 — WishubMemoryMiddleware 长期记忆
812
+
813
+ ✅ memory/ 模块: MemoryItem, RetrieveRequest, RetrieveResponse 数据模型
814
+ ✅ WishubMemoryClient — 异步 HTTP 客户端,对接 Wishub Memory API
815
+ - 优雅降级: 超时/错误返回空列表,不影响对话
816
+ - 完整日志: debug/info/warning/error 四级
817
+ ✅ MemoryPromptFormatter — 记忆列表 → 结构化 XML 提示词片段
818
+ - 支持相关度评分显示
819
+ - 空记忆占位符
820
+ - 可选使用指南
821
+ ✅ WishubMemoryMiddleware — DeepAgents 中间件
822
+ - abefore_agent: 检索记忆并存入 state
823
+ - modify_request: 注入记忆到系统提示词
824
+ - 跳过重复检索
825
+ - user_id/query 缺失时优雅处理
826
+ ✅ wrap_agent config 注入 — user_id, thread_id 传递到 LangGraph config.configurable
827
+ ✅ 完整测试覆盖 (39 个测试用例全部通过)
828
+
785
829
  Phase 4: 高级特性
786
830
  ════════════════
787
- 目标: 用户故事 6 — Checkpoint、多协议
831
+ 目标: 用户故事 7 — Checkpoint、多协议
788
832
 
789
833
  □ CheckpointClient + 自动持久化
790
834
  □ AGUIHandler
791
835
  □ Stop endpoint
792
836
  □ ApiKeyMiddleware
793
837
 
794
- Phase 4: 多框架 + 多协议
838
+ Phase 5: 多框架 + 多协议
795
839
  ══════════════════════
796
840
  目标: 验证可扩展性
797
841
 
@@ -817,12 +861,19 @@ wisruntime/
817
861
  │ └── client.py # MCPClient(ToolClient) — 薄封装标准 mcp 包
818
862
  │
819
863
  ├── adapters/ # Adapter + Capability Bridge
864
+ │ ├── memory/ # Memory 记忆检索适配器 (✅ v0.4)
865
+ │ │ ├── __init__.py # 导出 WishubMemoryClient, MemoryPromptFormatter, MemoryItem
866
+ │ │ ├── model.py # MemoryItem, RetrieveRequest, RetrieveResponse (Pydantic 模型)
867
+ │ │ ├── client.py # WishubMemoryClient — 异步 HTTP 客户端,优雅降级
868
+ │ │ └── formatter.py # MemoryPromptFormatter — 记忆列表 → XML 提示词片段
869
+ │ │
820
870
  │ └── langgraph/ # LangGraph/LangChain/DeepAgents 框架 (✅ v0.3)
821
- │ ├── __init__.py # 导出 LangGraphEventConverter, wrap_agent, LangChainToolAdapter, create_model, create_mcp_tools
871
+ │ ├── __init__.py # 导出 LangGraphEventConverter, wrap_agent, LangChainToolAdapter, create_model, create_mcp_tools, WishubMemoryMiddleware
822
872
  │ ├── converter.py # LangGraphEventConverter (astream_events → CanonicalEvent)
823
- │ ├── wrap_agent.py # wrap_agent() 糖函数
873
+ │ ├── wrap_agent.py # wrap_agent() 糖函数 (注入 config: user_id, thread_id)
824
874
  │ ├── tool_adapter.py # LangChainToolAdapter (ToolInfo → StructuredTool)
825
- │ └── builtin.py # create_model() / create_mcp_tools() 一行接入
875
+ │ ├── builtin.py # create_model() / create_mcp_tools() 一行接入
876
+ │ └── memory_middleware.py # WishubMemoryMiddleware — DeepAgents 记忆中间件
826
877
  │
827
878
  ├── server/ # Server 层
828
879
  │ ├── __init__.py
@@ -12,14 +12,21 @@ wisruntime.egg-info/requires.txt
12
12
  wisruntime.egg-info/top_level.txt
13
13
  wisruntime/adapters/__init__.py
14
14
  wisruntime/adapters/langgraph/__init__.py
15
+ wisruntime/adapters/langgraph/_utils.py
15
16
  wisruntime/adapters/langgraph/builtin.py
16
17
  wisruntime/adapters/langgraph/converter.py
18
+ wisruntime/adapters/langgraph/memory_middleware.py
17
19
  wisruntime/adapters/langgraph/tool_adapter.py
18
20
  wisruntime/adapters/langgraph/wrap_agent.py
21
+ wisruntime/adapters/memory/__init__.py
19
22
  wisruntime/conversation/__init__.py
20
23
  wisruntime/conversation/client.py
21
24
  wisruntime/conversation/model.py
22
25
  wisruntime/conversation/recorder.py
26
+ wisruntime/memory/__init__.py
27
+ wisruntime/memory/client.py
28
+ wisruntime/memory/formatter.py
29
+ wisruntime/memory/model.py
23
30
  wisruntime/server/__init__.py
24
31
  wisruntime/server/dify_handler.py
25
32
  wisruntime/server/invoker.py
File without changes
File without changes