Metadata-Version: 2.4
Name: wisruntime
Version: 0.3.1
Summary: 企业级 Agent Runtime SDK — 让 AI 工程师专注 Agent 能力开发，无需关心底层基础设施
Author: WIS Team
License: MIT
Project-URL: Homepage, https://github.com/infinitus-devops/aip-wisruntime
Project-URL: Repository, https://github.com/infinitus-devops/aip-wisruntime
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Classifier: Framework :: FastAPI
Requires-Python: >=3.11
Description-Content-Type: text/markdown
Requires-Dist: fastapi>=0.110
Requires-Dist: uvicorn[standard]>=0.27
Requires-Dist: sse-starlette>=2.0
Requires-Dist: pydantic>=2.0
Requires-Dist: httpx>=0.27
Provides-Extra: dev
Requires-Dist: pytest>=8.0; extra == "dev"
Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
Requires-Dist: httpx>=0.27; extra == "dev"
Provides-Extra: mcp
Requires-Dist: mcp>=1.0; extra == "mcp"

# 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](https://github.com/Serverless-Devs/agentrun-sdk-python) 的分层设计思想，在其基础上针对 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()` 操作。

```python
# 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` (规划中) | 同 ToolClient 协议，不同实现 | — |
| `SandboxClient` (规划中) | 代码/浏览器沙箱执行 | — |

### 4.3 Capability Bridge 层 (`adapters/langgraph/`)

**核心原则: 将 ToolClient 的 ToolInfo 转换为 LangChain StructuredTool。**

```python
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)
```

**核心能力**: `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 — 归一化请求模型**

```python
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 — 组装**

```python
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 工程师，未来 Skill 接入与 MCP 使用相同的 ToolClient   │
│           协议和 LangChainToolAdapter，零学习成本                        │
│                                                                   │
│  验收标准:                                                          │
│    # SkillClient 实现 ToolClient 协议，LangChainToolAdapter 无需改动  │
│    from wisruntime.tool import ToolClient                            │
│    from wisruntime.adapters.langgraph.tool_adapter import LangChainToolAdapter│
│    tools = await LangChainToolAdapter.from_client(SkillClient(...))   │
│    agent = create_deep_agent(model=model, tools=tools)               │
└─────────────────────────────────────────────────────────────────────┘

故事 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 数据模型
  □ 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/, 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 架构文档](event/agentrun-architecture.md) — 阿里云 AgentRun SDK 完整架构
- [LangChain 流式事件详解](event/LangChain流式事件详解.md) — v2/v3 事件体系
- [Dify vs 灵知 流式事件差异分析](event/diff-analysis.md) — 协议兼容性分析
- [PRD.md](PRD.md) — 原始产品需求文档
