wisruntime — 企业级 Agent Runtime SDK
版本: v0.1.1 (Adapter Layer) | 日期: 2026-07 | 团队: WIS Team
一、项目概述
1.1 定位
wisruntime 是一个企业级 Agent Runtime SDK(Python),以 pip install 方式提供给 AI 应用工程师使用。
它的核心使命:让 Agent 工程师专注 Agent 能力开发,无需关心底层基础设施。
1.2 一句话描述
为 Agent 开发者提供轻量、可扩展、多框架适配的统一运行时 SDK——用框架原生 API 构建 Agent 后,数行代码即可获得完整的生产级服务端点,并自动接入 WIS Hub 平台能力(MCP / Skill / Sandbox)。
1.3 核心价值
没有 wisruntime 有了 wisruntime
══════════════ ══════════════
AI 工程师 → 写 FastAPI 路由 AI 工程师 → 写 invoke_agent 函数
→ 处理 SSE 流式协议 → create_server(invoke_agent).start(8000)
→ 对接 MCP 协议 → ✅ Dify SSE 服务就绪
→ 对接 Skill API → ✅ 平台能力自动接入
→ 管理 Sandbox → ✅ API-KEY 统一鉴权
→ 对接鉴权系统 → ✅ 开箱即用
→ 每个应用都重复一遍
1.4 对标参考
本架构深度借鉴了阿里云 AgentRun Python SDK 的分层设计思想,在其基础上针对 WIS Hub 平台的差异化特点(统一 API-KEY 鉴权、Dify SSE 协议、DeepAgents 框架、平台能力托管)进行了重新设计。
二、整体架构
2.1 分层架构图
┌──────────────────────────────────────────────────────────────────────┐
│ WIS Hub 平台 (AI 中台) │
│ │
│ MCP 服务 │ Skill 服务 │ Sandbox 服务 │ Checkpoint 服务 │
│ (REST) │ (REST) │ (REST) │ (REST) │
│ │
│ 统一 API-KEY 鉴权: 创建应用 → 勾选能力 → 生成 Key │
└────────────────────────────┬─────────────────────────────────────────┘
│ HTTPS + Bearer <API-KEY>
│
┌────────────────────────────┴─────────────────────────────────────────┐
│ wisruntime SDK │
│ │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ Infrastructure 层 │ │
│ │ Config (API-KEY / 端点 / 超时) │ │
│ │ CredentialContext (contextvars: 请求级 API-KEY 注入) │ │
│ │ BaseModel / 异常定义 / 日志 │ │
│ └────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ Platform Client 层 (框架无关,只写一次) │ │
│ │ │ │
│ │ tool/ToolClient (ABC) + MCPClient │ │
│ │ tool/ToolInfo, ToolResult (shared models) │ │
│ │ │ │
│ │ 原则: 不重复造轮子,直接使用标准 mcp Python 包 │ │
│ └────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ Capability Bridge 层 (每个框架一份) │ │
│ │ │ │
│ │ adapters/langgraph/ │ │
│ │ ├── tool_adapter.py ToolClient → LangChain StructuredTool │ │
│ │ └── builtin.py create_model / create_mcp_tools() │ │
│ │ │ │
│ │ │ │
│ │ adapters/agentscope/ (未来) │ │
│ └────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ Adapter 层 (用户可选工具: Agent 对象 → invoke_agent 函数) │ │
│ │ │ │
│ │ 提供两种使用方式: │ │
│ │ ① wrap_agent(agent) → invoke_agent 函数 (便捷包装) │ │
│ │ ② 用户在 invoke_agent 内部手动做事件转换 │ │
│ │ │ │
│ │ adapters/langgraph/ (DeepAgents 事件 → CanonicalEvent) │ │
│ │ adapters/agentscope/ (未来) │ │
│ └────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ Server 层 (框架无关,只认函数) │ │
│ │ │ │
│ │ ┌──────────────┐ ┌──────────────┐ ┌───────────────────┐ │ │
│ │ │ DifyHandler │ │AGUIHandler │ │ AgentInvoker │ │ │
│ │ │ SSE+Blocking │ │ (规划中) │ │ 函数归一化引擎 │ │ │
│ │ └──────┬───────┘ └──────┬───────┘ └─────────┬─────────┘ │ │
│ │ └─────────────────┴────────────────────┘ │ │
│ │ │ │ │
│ │ CanonicalEvent │ │
│ │ ProtocolRegistry │ │
│ └────────────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────────┘
2.2 分层职责
| 层 | 职责 | 关键组件 |
|---|---|---|
| Infrastructure | 全局配置、凭证注入、基础模型 | Config, CredentialContext, BaseModel |
| Platform Client | 平台工具接入层,抽象协议 + MCP 实现 | ToolClient (ABC), MCPClient, ToolInfo, ToolResult |
| Capability Bridge | ToolClient → LangChain StructuredTool 转换 | LangChainToolAdapter, create_mcp_tools() |
| Adapter | (用户可选) Agent 对象 → invoke_agent 函数包装,及框架事件 → CanonicalEvent 转换 | wrap_agent(), LangGraphEventConverter |
| Server | HTTP 服务承载、协议路由、请求校验、响应序列化。只接收 invoke_agent 函数 | DifyHandler, AgentInvoker, ProtocolRegistry |
2.3 关键设计决策
| 决策 | 说明 | 参考来源 |
|---|---|---|
| API-KEY 唯一鉴权 | 不引入 AK/SK/STS 签名体系,整个应用只用一个 API-KEY | WIS Hub 平台特征 |
| SDK 不管理资源生命周期 | 资源由 WIS Hub 平台管理,SDK 只做运行时调用(零 CRUD) | 与 AgentRun 的核心差异 |
| Platform Client 框架无关 | MCP/Skill/Sandbox 客户端只写一次,所有框架共享 | AgentRun Resource 层思想 |
| Capability Bridge 每个框架一份 | 将平台客户端转为框架原生 Tool 对象 | AgentRun Integration 层思想 |
| CanonicalEvent 13 种类型 | 综合 LangChain v2/v3、Dify/Wisknow、AgentRun 四套事件体系 | 见第三节 |
| Server 只接收 invoke_agent 函数 | Server 不知道框架的存在,用户函数 = 唯一入口。与 AgentRun 完全一致 | AgentRun AgentRunServer |
| AgentInvoker 单层归一化 | 用户函数 yield 的 str/AgentEvent → CanonicalEvent 流 | AgentRun AgentInvoker 模式 |
| 协议处理器双向负责 | Handler 负责请求解析 + 响应序列化 | AgentRun Protocol Handler 模式 |
| contextvars 注入 API-KEY | Server 模式下从请求 Bearer 头提取 → 注入请求上下文 | AgentRun CredentialContext 模式 |
三、CanonicalEvent 设计
CanonicalEvent 是 wisruntime 的核心抽象——框架事件和协议事件之间唯一的稳定契约。所有 Adapter 产出 CanonicalEvent,所有 ProtocolHandler 消费 CanonicalEvent。
3.1 13 种 CanonicalEvent 类型
┌──────────────────────────────────────────────────────────────────────┐
│ CanonicalEvent 类型体系 (13 种) │
├──────────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ 内容流事件 │ │
│ │ TextDelta 文本增量 (token 级) │ │
│ │ ReasoningDelta 推理增量 (CoT 思维链 / extended thinking) │ │
│ │ FileOutput 文件输出 (图片/音频/视频/文件) │ │
│ └──────────────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ 工具调用生命周期 │ │
│ │ ToolCallStart 工具调用开始 (name + args_delta 分块流式) │ │
│ │ ToolCallEnd 工具调用结束 (result 或 error) │ │
│ └──────────────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ 子Agent 生命周期 (DeepAgents 子智能体委派) │ │
│ │ SubAgentStart 子Agent 开始运行 (agent_name, run_id, task) │ │
│ │ SubAgentEnd 子Agent 运行结束 (status: completed/failed) │ │
│ └──────────────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ 图执行生命周期 (🆕 v0.2) │ │
│ │ NodeLifecycleEvent LangGraph 节点生命周期 │ │
│ │ (node_name, node_type, phase=start/end, run_id, metadata) │ │
│ └──────────────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ 人机协作 (🆕 v0.2) │ │
│ │ InterruptEvent Agent 暂停等待人工输入/审批 │ │
│ │ (interrupt_id, node_name, message, data) │ │
│ └──────────────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ 元事件 │ │
│ │ Usage Token 用量统计 (prompt/completion/total) │ │
│ │ Error 错误事件 (message, code, recoverable) │ │
│ │ Custom 自定义事件 (name + payload 扩展通道) │ │
│ │ Done 流结束标记 (由 AgentInvoker 自动插入) │ │
│ └──────────────────────────────────────────────────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────────────┘
3.2 LangGraph → CanonicalEvent → Dify SSE 完整事件映射表
下表展示从 LangGraph astream_events v2 事件,经 LangGraphEventConverter 转换为 CanonicalEvent,再经 DifyHandler 序列化为 Dify SSE 格式的完整链路。
LangGraph astream_events v2 CanonicalEvent Dify SSE
══════════════════════════ ════════════════ ════════════
on_chat_model_stream
chunk.content (str) TextDeltaEvent agent_message
delta=<content> { event, task_id, id<message_id>,
answer }
chunk.tool_call_chunks[] ToolCallStartEvent agent_thought
first chunk: {id, name, args} tool_call_id=<id> { event, task_id,
subsequent: {index, args} name=<name> id<tool_call_id>,
args_delta=<args> message_id, position,
tool, tool_input }
chunk.reasoning_content ReasoningDeltaEvent reasoning_chunk
(DeepSeek/Claude thinking) delta=<reasoning> { event, task_id,
reasoning }
on_chat_model_end
output.token_usage UsageEvent (累积到 message_end
prompt/completion/total metadata.usage)
on_tool_start
name="task" SubAgentStartEvent subagent_start
input.subagent_type agent_name=<type> { agent_name, run_id,
input.description task_description=<desc> task_description }
name≠"task" ToolCallStartEvent agent_thought
(if not via stream) (去重,避免重复发送) (同上)
on_tool_end
name="task" SubAgentEndEvent subagent_end
output status="completed" { agent_name, run_id, status }
name≠"task" ToolCallEndEvent agent_thought
output=<result> result=<formatted> { observation }
on_tool_error ErrorEvent error
code="TOOL_ERROR" { message, code, status=500 }
on_chain_start NodeLifecycleEvent node_started
name="model" / "tools" / ... phase="start" { node_id<run_id>,
node_type=<inferred> node_type, title }
on_chain_end NodeLifecycleEvent node_finished
phase="end" { node_id, node_type, title }
on_custom_event CustomEvent { event: <name>,
(from get_stream_writer) name=<event_name> ...payload }
payload=<data>
on_chain_error / ErrorEvent error
on_llm_error / code="CHAIN_ERROR" / { message, code, status=500 }
on_retriever_error "LLM_ERROR" / ...
interrupt() / interrupt_on InterruptEvent workflow_paused
(LangGraph interrupt) interrupt_id, node_name, { message, node_name }
message, data
(user fn 正常结束) DoneEvent message_end
(AgentInvoker 自动插入) metadata { task_id, id<message_id>,
metadata: { usage,
conversation_id },
files: [] }
3.3 数据流
┌─── invoke_agent 函数 (用户编写) ───┐
│ │
│ 用户可以在函数内部: │
│ · 直接 yield str / CanonicalEvent │
│ · 使用 wrap_agent(agent) 自动转换 │
│ · 手写 LangGraphEventConverter │
│ · 调用 MCPClient / SkillClient │
│ │
└────────────┬───────────────────────┘
│
▼
┌─────────────────────────┐
│ AgentInvoker │ ← 归一化:
│ │ str → TextDelta
│ AsyncGenerator[ │ chunk → TextDelta+ReasoningDelta
│ CanonicalEvent] │ 异常 → Error, 透传 CanonicalEvent
│ │ 自动 Done, 同步生成器支持
└────────────┬────────────┘
│
▼
┌─────────────────────────┐
│ ProtocolHandler │ ← 序列化: CanonicalEvent → SSE/JSON
│ │
│ DifyHandler → Dify SSE │ 生成 message_id
│ (未来) AGUIHandler │ 回传 conversation_id
└─────────────────────────┘
Adapter 层: wrap_agent → LangGraphEventConverter → CanonicalEvent 流 → yield
四、分层详解
4.1 Infrastructure 层
┌────────────────────────────────────────────────────┐
│ Config │
│ ├── api_key: str 统一 API-KEY │
│ ├── hub_endpoint: str WIS Hub 地址 │
│ ├── timeout: int = 600 请求超时(秒) │
│ └── 获取优先级: │
│ 1. 显式传入 Config(api_key="...") │
│ 2. 环境变量 WIS_HUB_API_KEY │
│ 3. 请求级 contextvars overlay (Server 模式) │
│ │
│ CredentialContext (contextvars) │
│ ├── get_request_api_key() → Optional[str] │
│ ├── set_request_api_key(key) 上下文管理器 │
│ └── 用途: Server 模式从 Bearer 头注入 → 全局可用 │
│ │
│ ApiKeyMiddleware (纯 ASGI) │
│ ├── 每次请求从 Authorization: Bearer <KEY> 解析 │
│ ├── 注入 contextvars overlay │
│ └── 请求结束时自动复位 │
└────────────────────────────────────────────────────┘
与 AgentRun 的差异: AgentRun 使用 AK/SK + STS + RAM 签名三级体系,wisruntime 只使用 API-KEY,结构大幅简化。但 contextvars 注入模式完全复用。
4.2 Platform Client 层 (tool/)
核心原则: 框架无关,只写一次。ToolClient 抽象协议 + MCP 实现。
WIS Hub 平台负责资源的创建和管理,SDK 不需要 create() / delete() 操作。
# ToolClient 抽象协议
class ToolClient(ABC):
async def list_tools(self) -> list[ToolInfo]: ...
async def call_tool(self, name: str, arguments: dict) -> ToolResult: ...
async def close(self): ...
# MCP 协议实现 (薄封装标准 mcp 包)
class MCPClient(ToolClient):
def __init__(self, url=None, api_key=None, timeout=600):
self._url = url or f"{Config().hub_endpoint}/wishub-mcp/mcp"
self._api_key = api_key or Config().api_key # 三级获取
async def list_tools(self) -> list[ToolInfo]: # async 不加后缀
...
async def call_tool(self, name, arguments=None) -> ToolResult:
...
def list_tools_sync(self) -> list[ToolInfo]: # sync 糖加 _sync 后缀
...
各 Client 职责:
| Client | 职责 | 端点示例 |
|---|---|---|
MCPClient (已实现) |
MCP Streamable HTTP 工具发现与调用 | POST /wishub-mcp/mcp |
SkillClient (已实现) |
Skill 知识包发现/详情/下载 — 本地化注入,不走 ToolClient 协议 | GET /wishub-skill/openapi/v1/skills |
SandboxClient (规划中) |
代码/浏览器沙箱执行 | — |
4.3 Capability Bridge 层 (adapters/langgraph/)
核心原则: 将 ToolClient 的 ToolInfo 转换为 LangChain StructuredTool。
from wisruntime.tool import MCPClient
from wisruntime.adapters.langgraph import create_mcp_tools, LangChainToolAdapter
from deepagents import create_deep_agent
# 方式 A: 一行获取 MCP 工具并创建 Agent(推荐)
tools = await create_mcp_tools() # 自动读取 WIS_HUB_API_KEY
agent = create_deep_agent(model=model, tools=tools)
# 方式 B: 手动控制 — 创建 client,桥接所有工具
client = MCPClient()
tools = await LangChainToolAdapter.from_client(client)
agent = create_deep_agent(model=model, tools=tools)
# 方式 C: 过滤只接入部分工具
tools = await LangChainToolAdapter.from_client(
client, filter_fn=lambda t: "search" in t.name
)
# 方式 D: 单工具手动创建
adapter = LangChainToolAdapter.from_tool_info(tool_info, my_callable)
Skill 是另一种接入模式——知识包下载到本地后注入 deepagents(不走 ToolClient):
from wisruntime.adapters.langgraph.builtin import create_skill_sources
# 一行下载并返回 deepagents skills= 参数(skillCode 为平台唯一标识)
skill_paths = await create_skill_sources(
remote_skills=["skill_k67go1cujb6ji"],
skill_versions={"skill_k67go1cujb6ji": "1.0.4"}, # 严格 SemVer,可选
)
agent = create_deep_agent(model=model, tools=tools, skills=skill_paths)
Skill 客户端完整用法(发现/详情/批量下载)见 docs/wishub-skill-api.md。
核心能力: from_tool_info() 将 ToolInfo + 调用函数转为 StructuredTool(BaseTool 子类),内部自动完成 JSON Schema → Pydantic Model 的动态生成,无需手写参数校验。from_client() / create_mcp_tools() 一键发现所有工具并批量创建。MCPClient 无状态设计——每次 call_tool() 自包含 session,无需显式管理生命周期。
4.4 Adapter 层
定位: 用户可选工具层。Server 不调用 Adapter,Adapter 是用户在 invoke_agent 中使用的辅助工具。
与 AgentRun 的设计一致:AgentRun 的 AgentRunConverter(仅 LangGraph 需要)是用户在 invoke_agent 内部自行使用的,不是 Server 的依赖。
两种使用方式:
方式 A: 用 wrap_agent 把 Agent 对象包装成 invoke_agent 函数 (便捷)
════════════════════════════════════════════════════════════════════
from wisruntime.adapters.langgraph import wrap_agent
agent = create_deep_agent(tools=[...])
create_server(wrap_agent(agent)).start(8000)
# wrap_agent 内部: Agent.astream_events → CanonicalEvent 流 → invoke_agent 函数
方式 B: 在 invoke_agent 内部手动做事件转换 (完全控制)
════════════════════════════════════════════════════════════════════
from wisruntime.adapters.langgraph import LangGraphEventConverter
async def my_invoke_agent(request: AgentRequest):
agent = create_deep_agent(tools=[...])
adapter = DeepAgentsAdapter()
async for event in agent.astream_events(request.messages):
canonical = adapter.convert(event) # 框架事件 → CanonicalEvent
if canonical:
yield canonical
create_server(my_invoke_agent).start(8000)
Adapter 内部需要处理的核心问题 (参考 AgentRun AgentRunConverter):
| 问题 | 解决方案 |
|---|---|
| 流式工具调用 ID 追踪 | 维护 tool_call_index → id 映射,ToolCallStart 时携带 id |
| 推理/文本分离 | 检测 reasoning_content 或 reasoning-delta → ReasoningDelta |
| 结束检测 | v2: on_chain_end + name="LangGraph"; v3: message-finish |
| 错误降级 | on_tool_error → ToolCallEnd(error=...),不升级为全局 Error |
4.5 Server 层
核心原则: 与 AgentRun 完全一致——只接收 invoke_agent 函数,不感知框架。
┌──────────────────────────────────────────────────────────────┐
│ WisRuntimeServer │
│ │
│ 构造: create_server(invoke_agent, protocols=[DifyHandler()]) │
│ │
│ 初始化流程: │
│ 1. 创建 FastAPI app │
│ 2. 注入 ApiKeyMiddleware → contextvars │
│ 3. (可选) checkpoint 自动持久化包装 │
│ 4. 创建 AgentInvoker(invoke_agent) │
│ 5. 注册 /health 路由 │
│ 6. 挂载 ProtocolHandler 路由 │
│ │
│ 两种使用模式: │
│ │
│ 模式 1: 独立服务 (开发/调试) │
│ create_server(invoke_agent).start(port=8000) │
│ │
│ 模式 2: FastAPI 子应用 (生产推荐) │
│ app.mount("/agent", create_server(invoke_agent).as_fastapi_app())│
└──────────────────────────────────────────────────────────────┘
AgentInvoker — 唯一的归一化入口
invoke_agent 函数 → AgentInvoker.invoke(fn, request) → CanonicalEvent 流
自动检测:
├── async generator → 直接迭代
├── async function → await 得到返回值,单次 yield
├── sync function → loop.run_in_executor(线程池) + contextvars 传播
├── str → CanonicalEvent(type=TextDelta, data={delta: str})
├── CanonicalEvent → 透传
├── None → 跳过
└── Exception → CanonicalEvent(type=Error, ...)
设计决策:
├── Done 事件: AgentInvoker 在 generator 耗尽时自动插入
│ (Dify 协议必须有 message_end,AgentRun 不需要)
├── str 语义: 用户 yield 的 str 就是模型的文本输出 → TextDelta
│ 高级类型 (Reasoning/ToolCall) 用户通过直接 yield CanonicalEvent 表达
└── 函数签名: 不强约束,运行时 inspect 检测 (与 AgentRun 一致)
用户函数接收 AgentRequest 作为第一个位置参数
AgentRequest — 归一化请求模型
class Message(BaseModel):
role: Literal["system", "user", "assistant", "tool"]
content: str | list[dict] # 文本或多模态
name: str | None = None
class AgentRequest(BaseModel):
messages: list[Message] # 归一化消息列表
stream: bool = True # 是否流式
conversation_id: str | None = None # 会话ID
user_id: str | None = None # 用户ID
headers: dict[str, str] # 关键 header 副本 (可跨线程)
raw_request: Request | None = None # 原始 Starlette Request (本地可用)
protocol_extensions: dict = {} # 协议特有字段 (如 Dify 的 inputs/files)
ProtocolHandler — 响应序列化
┌─────────────────────────────────────────────────────────┐
│ DifyHandler │
│ │
│ 端点: │
│ POST /dify/v1/chat-messages SSE Streaming │
│ POST /dify/v1/chat-messages Blocking (stream=false) │
│ POST /dify/v1/chat-messages/{task_id}/stop (Phase 3) │
│ │
│ CanonicalEvent → Dify SSE 映射: │
│ TextDelta → agent_message │
│ { task_id, id<message_id>, answer }│
│ ReasoningDelta → reasoning_chunk │
│ { task_id, reasoning } │
│ ToolCallStart → agent_thought │
│ { task_id, id<tool_call_id>, │
│ message_id, position, │
│ tool, tool_input } │
│ ToolCallEnd → agent_thought │
│ { task_id, id<tool_call_id>, │
│ message_id, position, │
│ observation } │
│ SubAgentStart → subagent_start │
│ SubAgentEnd → subagent_end │
│ FileOutput → message_file │
│ NodeLifecycleEvent → node_started / node_finished 🆕 │
│ InterruptEvent → workflow_paused 🆕 │
│ Usage → (累积到 message_end.metadata.usage) │
│ Error → error { message, code, status } │
│ Custom → { event: name, ...payload } │
│ Done → message_end │
│ { task_id, id<message_id>, │
│ metadata: { usage, │
│ conversation_id }, │
│ files: [] } │
│ │
│ DifyHandler 关键 ID 管理 (🆕 v0.2): │
│ ├── task_id: 请求级 UUID,所有 SSE 事件共享 │
│ ├── message_id: 助手消息 UUID,贯穿 agent_message、 │
│ │ agent_thought、message_end │
│ ├── conversation_id: 请求传入 → message_end.metadata 回传 │
│ ├── agent_thought.id: 使用 ToolCallStart.tool_call_id │
│ │ (不再新生成 UUID,确保 start/end 配对一致) │
│ └── 心跳: 每 10 秒自动发送 data: {"event": "ping"}\n\n │
│ (asyncio background task) │
│ │
│ AGUIHandler (规划中) │
│ CanonicalEvent → AG-UI SSE 格式 │
└─────────────────────────────────────────────────────────┘
WisRuntimeServer — 组装
class ServerConfig(BaseModel):
cors_origins: list[str] = ["*"]
health_path: str = "/health"
class WisRuntimeServer:
def __init__(self, invoker: AgentInvoker, protocols: list[ProtocolHandler], config: ServerConfig):
# 1. 创建 FastAPI app
# 2. (Phase 2) 注入 ApiKeyMiddleware
# 3. for handler in protocols: handler.install(app, invoker)
# 4. 注册 health 路由
# 5. 配置 CORS
def start(self, host="0.0.0.0", port=8000):
"""模式 1: 独立服务 (内部调 uvicorn.run)"""
def as_fastapi_app(self) -> FastAPI:
"""模式 2: 导出为 FastAPI 子应用,供 app.mount()"""
# 模式 3 (as_router) 不做。用户如需 include_router 级别的控制,
# 可以手动创建 AgentInvoker + DifyHandler 自行组装。
def create_server(
invoke_agent: Callable,
*,
protocols: list[ProtocolHandler] | None = None, # 默认 [DifyHandler()]
config: ServerConfig | None = None,
checkpoint_enabled: bool = False, # Phase 3
) -> WisRuntimeServer:
...
五、用户故事与实施蓝图
5.1 用户故事总览
故事 1 ──────────────────────────────────────────────────────────────
│ 用户故事: 作为 AI 工程师,我只需写一个 invoke_agent 函数, │
│ 就能获得一个 Dify 兼容的 SSE 服务端点 │
│ │
│ 验收标准: │
│ from wisruntime import create_server │
│ from wisruntime.model import AgentRequest │
│ │
│ async def my_invoke_agent(request: AgentRequest): │
│ # 用户可以自由使用任何框架、任意逻辑 │
│ async for chunk in llm.astream(request.messages): │
│ yield chunk.content # yield str 即可 │
│ │
│ create_server(my_invoke_agent).start(port=8000) │
│ # → curl -X POST http://localhost:8000/dify/v1/chat-messages │
│ # -d '{"query": "你好", "response_mode": "streaming"}' │
│ # → SSE 流式响应,包含 agent_message / agent_thought / message_end│
└─────────────────────────────────────────────────────────────────────┘
故事 2 ──────────────────────────────────────────────────────────────
│ 用户故事: 作为 AI 工程师,我接入 WIS Hub MCP 服务时, │
│ 只需创建一个 MCPClient,一行代码桥接为 DeepAgents 工具 │
│ │
│ 验收标准: │
│ from wisruntime.adapters.langgraph import create_mcp_tools │
│ from deepagents import create_deep_agent │
│ │
│ tools = await create_mcp_tools() # 一行,自动读 WIS_HUB_API_KEY │
│ agent = create_deep_agent(model=model, tools=tools) │
│ # → Agent 调用工具时,MCPClient 自动创建 session 并携带 Bearer 头 │
└─────────────────────────────────────────────────────────────────────┘
故事 3 ──────────────────────────────────────────────────────────────
│ 用户故事: 作为 AI 工程师,我可以将 wisruntime 服务嵌入到已有 │
│ FastAPI 应用中,作为子路由挂载 │
│ │
│ 验收标准: │
│ from fastapi import FastAPI │
│ app = FastAPI() │
│ app.mount("/agent", create_server(my_invoke_agent).as_fastapi_app())│
│ # → /agent/dify/v1/chat-messages 就绪 │
│ # → 复用主应用的 middleware 和 lifecycle │
└─────────────────────────────────────────────────────────────────────┘
故事 4 ──────────────────────────────────────────────────────────────
│ 用户故事: 作为 AI 工程师,我用 wrap_agent 包装 deepagents Agent, │
│ 一行代码就生成 invoke_agent 函数 │
│ │
│ 验收标准: │
│ from deepagents import create_deep_agent │
│ from wisruntime.adapters.langgraph import wrap_agent │
│ │
│ agent = create_deep_agent(tools=[...]) │
│ create_server(wrap_agent(agent)).start(8000) │
│ # → wrap_agent 内部: astream_events → CanonicalEvent → yield │
└─────────────────────────────────────────────────────────────────────┘
故事 5 ──────────────────────────────────────────────────────────────
│ 用户故事: 作为 AI 工程师,我可以用一行代码从 WIS Hub 下载 Skill 知识包 │
│ 并注入 deepagents,Agent 自动获得平台沉淀的领域能力 │
│ │
│ 验收标准: │
│ # skillCode 为平台唯一标识;下载后注入 deepagents skills= │
│ from wisruntime.adapters.langgraph.builtin import ( │
│ create_skill_sources) │
│ skill_paths = await create_skill_sources( │
│ remote_skills=["skill_k67go1cujb6ji"], │
│ skill_versions={"skill_k67go1cujb6ji": "1.0.4"}, │
│ ) │
│ agent = create_deep_agent(model=model, skills=skill_paths) │
└─────────────────────────────────────────────────────────────────────┘
故事 6 ──────────────────────────────────────────────────────────────
│ 用户故事: 作为 AI 工程师,我的 Agent 对话历史可以自动持久化到 │
│ WIS Hub Checkpoint 服务,支持断点续传 │
│ │
│ 验收标准: │
│ create_server(my_invoke_agent, checkpoint_enabled=True).start(8000) │
│ # → 每次对话自动保存到 WIS Hub │
│ # → 下次请求带有相同 conversation_id 时恢复上下文 │
└─────────────────────────────────────────────────────────────────────┘
5.2 实施阶段
Phase 0: 项目骨架 (已完成)
══════════════════════════
✅ 清理旧代码,确定新的架构方向
✅ 分析 AgentRun、LangChain、Dify 参考架构
✅ 确定 CanonicalEvent 11 种类型
✅ 确定分层架构
Phase 1: 核心基础设施 + MVP Server (✅ 已完成)
═══════════════════════════════════════════
目标: 用户故事 1 — 写 invoke_agent 函数,获得 Dify SSE 端点
✅ CanonicalEvent 13 种类型定义 (v0.2 扩至 13 种)
✅ AgentRequest + Message 模型
✅ Config + CredentialContext (contextvars 定义)
✅ AgentInvoker (async/sync/iterator 检测, str→TextDelta,
模型 chunk 智能提取, 同步生成器支持, 自动 Done)
✅ DifyHandler (parse_request + _format_stream + 心跳 + Blocking)
- message_id 生成与全链路透传
- agent_thought.id 使用 tool_call_id (start/end 配对一致)
- conversation_id 回传
✅ WisRuntimeServer (start + as_fastapi_app)
✅ ProtocolRegistry + create_server 顶层 API
✅ create_server 自动检测 CompiledStateGraph → wrap_agent
Phase 2: LangGraph 适配器层 (✅ 已完成 — v0.2)
═══════════════════════════════════
目标: 用户故事 4 — wrap_agent、LangGraphEventConverter
✅ LangGraphEventConverter (astream_events v2 → CanonicalEvent)
- 工具调用 ID 追踪 (tool_call_id_map)
- 子智能体检测 (task 工具 → SubAgentStart/End)
- 节点生命周期 (on_chain_start/end → NodeLifecycleEvent)
- CustomEvent、ErrorEvent、UsageEvent 映射
- updates/values 兼容模式
✅ wrap_agent(agent_graph) 工厂函数
- 支持 astream_events (默认)、updates、values 三种模式
✅ AgentInvoker 模型 chunk 智能提取
- content → TextDeltaEvent
- reasoning_content → ReasoningDeltaEvent
✅ CanonicalEvent 扩展: +InterruptEvent, +NodeLifecycleEvent
✅ DifyHandler SSE 格式合规修复
- message_id, conversation_id, agent_thought id 一致性
Phase 3: 平台能力接入 (MCP ✅ 已完成 — v0.3)
═══════════════════════════════
目标: 用户故事 2 + 5 — MCP/Skill/Sandbox 接入
✅ tool/ 模块: ToolInfo, ToolResult, ToolClient (ABC), MCPClient
✅ MCPClient 无状态设计 (v0.3.1) — 每次调用自包含 session,无需生命周期管理
✅ LangChainToolAdapter: ToolInfo → StructuredTool(BaseTool 子类, JSON Schema → Pydantic)
✅ builtin.py: create_model() / create_mcp_tools() 一行接入
✅ ToolException + handle_tool_error 错误处理对齐 LangChain 标准
✅ SkillClient + Skill 数据模型 (v0.3.x) — 对接中台 wishub-skill openapi/v1:
分页发现 / 详情 / 302 预签名下载 / 严格 SemVer 版本, create_skill_sources() deepagents 集成
□ SandboxClient + Sandbox 数据模型
Phase 4: 高级特性
════════════════
目标: 用户故事 6 — Checkpoint、多协议
□ CheckpointClient + 自动持久化
□ AGUIHandler
□ Stop endpoint
□ ApiKeyMiddleware
Phase 4: 多框架 + 多协议
══════════════════════
目标: 验证可扩展性
□ AgentScope Adapter
□ AGUI Handler
□ 内部 PyPI 发布
□ 性能基准测试
5.3 目录结构 (目标态)
wisruntime/
├── __init__.py # 顶层导出: create_server, Config, ToolInfo, ToolResult
├── config.py # Config, CredentialContext
├── model.py # CanonicalEvent (13 种), AgentRequest, Message
├── exceptions.py # 自定义异常
│
├── tool/ # 平台工具接入层 (✅ v0.3 — 一个模块)
│ ├── __init__.py # exports: ToolClient, MCPClient, ToolInfo, ToolResult
│ ├── model.py # ToolInfo, ToolResult (工具共享数据契约)
│ ├── protocol.py # ToolClient (ABC) — 抽象协议
│ └── client.py # MCPClient(ToolClient) — 薄封装标准 mcp 包
│
├── adapters/ # Adapter + Capability Bridge
│ └── langgraph/ # LangGraph/LangChain/DeepAgents 框架 (✅ v0.3)
│ ├── __init__.py # 导出 LangGraphEventConverter, wrap_agent, LangChainToolAdapter, create_model, create_mcp_tools
│ ├── converter.py # LangGraphEventConverter (astream_events → CanonicalEvent)
│ ├── wrap_agent.py # wrap_agent() 糖函数
│ ├── tool_adapter.py # LangChainToolAdapter (ToolInfo → StructuredTool)
│ └── builtin.py # create_model() / create_mcp_tools() 一行接入
│
├── server/ # Server 层
│ ├── __init__.py
│ ├── server.py # WisRuntimeServer (FastAPI 组装)
│ ├── invoker.py # AgentInvoker (函数归一化)
│ ├── dify_handler.py # DifyHandler (SSE + Blocking)
│ └── protocol.py # ProtocolHandler 抽象基类
│
├── skill/ # Skill 知识包接入层 (✅ v0.3.x — wishub-skill openapi/v1)
│ ├── __init__.py # exports: SkillClient, SkillInfo, SkillDetailInfo, SkillPage
│ ├── model.py # SkillInfo / SkillDetailInfo / SkillPage(信封分页模型)
│ └── client.py # SkillClient — 分页发现 / 详情 / 302 预签名下载
│
└── (未来: sandbox/, checkpoint/) # 远程工具接入走 ToolClient 协议,不同实现
六、技术规格
6.1 运行时依赖
| 包 | 版本 | 用途 |
|---|---|---|
fastapi |
≥ 0.110 | Web 框架 |
uvicorn[standard] |
≥ 0.27 | ASGI 服务器 |
sse-starlette |
≥ 2.0 | SSE 流式响应 |
pydantic |
≥ 2.0 | 数据校验与序列化 |
httpx |
≥ 0.27 | 异步 HTTP 客户端 |
6.2 环境变量
| 变量 | 用途 | 必填 |
|---|---|---|
WIS_HUB_API_KEY |
WIS Hub 统一 API-KEY | 使用 MCP/Skill/Sandbox/Checkpoint 时必填 |
WIS_HUB_ENDPOINT |
WIS Hub 地址 | 可选,默认读取 SDK 内置配置 |
6.3 Python 版本
≥ 3.11
七、参考资源
- AgentRun Python SDK 架构文档 — 阿里云 AgentRun SDK 完整架构
- LangChain 流式事件详解 — v2/v3 事件体系
- Dify vs 灵知 流式事件差异分析 — 协议兼容性分析
- PRD.md — 原始产品需求文档
Metadata
Release files for wisruntime 0.4.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| wisruntime-0.4.0.tar.gz | 85.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| wisruntime-0.4.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 163.2 kB
Release files / wisruntime-0.4.0.tar.gz
| Download URL | wisruntime-0.4.0.tar.gz |
|---|---|
| Size | 85.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
387f6e54dd489c77c20a8e24fd18f3fdfae408d1ec6c5fe5301f87d5e9abf377
|
|
BLAKE2b-256 checksum How to use checksums |
6c4befb7a8bef17b7d067021e94f3b3d1647ee160c41628d3266ad6a0e285a0b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.5
|
Release files / wisruntime-0.4.0-py3-none-any.whl
| Download URL | wisruntime-0.4.0-py3-none-any.whl |
|---|---|
| Size | 77.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
a3ccab3c26bfd550bfbff7528c7cd5f83add8ffe085f34e1ac0aa3eaddffeded
|
|
BLAKE2b-256 checksum How to use checksums |
942547ce4b2dd9be3e816bebf0f5eb70dfa26973b9a1b97752d26445e8b631da
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.5
|