langchain-agentx-stream-ui-backend 0.1.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.
- langchain-agentx-stream-ui-backend-0.1.0/PKG-INFO +182 -0
- langchain-agentx-stream-ui-backend-0.1.0/README.md +174 -0
- langchain-agentx-stream-ui-backend-0.1.0/langchain_agentx_stream_ui_backend/__init__.py +84 -0
- langchain-agentx-stream-ui-backend-0.1.0/langchain_agentx_stream_ui_backend/buffer.py +406 -0
- langchain-agentx-stream-ui-backend-0.1.0/langchain_agentx_stream_ui_backend/buffer_protocol.py +34 -0
- langchain-agentx-stream-ui-backend-0.1.0/langchain_agentx_stream_ui_backend/errors.py +50 -0
- langchain-agentx-stream-ui-backend-0.1.0/langchain_agentx_stream_ui_backend/fixtures.py +573 -0
- langchain-agentx-stream-ui-backend-0.1.0/langchain_agentx_stream_ui_backend/lifecycle.py +34 -0
- langchain-agentx-stream-ui-backend-0.1.0/langchain_agentx_stream_ui_backend/middleware.py +60 -0
- langchain-agentx-stream-ui-backend-0.1.0/langchain_agentx_stream_ui_backend/permission.py +331 -0
- langchain-agentx-stream-ui-backend-0.1.0/langchain_agentx_stream_ui_backend/sse.py +521 -0
- langchain-agentx-stream-ui-backend-0.1.0/langchain_agentx_stream_ui_backend/subagent_contract.py +39 -0
- langchain-agentx-stream-ui-backend-0.1.0/langchain_agentx_stream_ui_backend/task_sync/__init__.py +47 -0
- langchain-agentx-stream-ui-backend-0.1.0/langchain_agentx_stream_ui_backend/task_sync/completion_hide.py +118 -0
- langchain-agentx-stream-ui-backend-0.1.0/langchain_agentx_stream_ui_backend/task_sync/dir_watcher.py +100 -0
- langchain-agentx-stream-ui-backend-0.1.0/langchain_agentx_stream_ui_backend/task_sync/disk_reset.py +12 -0
- langchain-agentx-stream-ui-backend-0.1.0/langchain_agentx_stream_ui_backend/task_sync/mapper.py +56 -0
- langchain-agentx-stream-ui-backend-0.1.0/langchain_agentx_stream_ui_backend/task_sync/models.py +41 -0
- langchain-agentx-stream-ui-backend-0.1.0/langchain_agentx_stream_ui_backend/task_sync/sdk_sync.py +34 -0
- langchain-agentx-stream-ui-backend-0.1.0/langchain_agentx_stream_ui_backend/task_sync/snapshot_event.py +85 -0
- langchain-agentx-stream-ui-backend-0.1.0/langchain_agentx_stream_ui_backend/task_sync/snapshot_store.py +93 -0
- langchain-agentx-stream-ui-backend-0.1.0/langchain_agentx_stream_ui_backend/task_sync/sse_bridge.py +278 -0
- langchain-agentx-stream-ui-backend-0.1.0/langchain_agentx_stream_ui_backend/task_sync/suppression.py +67 -0
- langchain-agentx-stream-ui-backend-0.1.0/langchain_agentx_stream_ui_backend/task_sync/watcher.py +234 -0
- langchain-agentx-stream-ui-backend-0.1.0/langchain_agentx_stream_ui_backend/transcript_gate_middleware.py +85 -0
- langchain-agentx-stream-ui-backend-0.1.0/langchain_agentx_stream_ui_backend/ui_adapter.py +53 -0
- langchain-agentx-stream-ui-backend-0.1.0/langchain_agentx_stream_ui_backend/versions.py +29 -0
- langchain-agentx-stream-ui-backend-0.1.0/langchain_agentx_stream_ui_backend/wrap.py +314 -0
- langchain-agentx-stream-ui-backend-0.1.0/langchain_agentx_stream_ui_backend.egg-info/PKG-INFO +182 -0
- langchain-agentx-stream-ui-backend-0.1.0/langchain_agentx_stream_ui_backend.egg-info/SOURCES.txt +33 -0
- langchain-agentx-stream-ui-backend-0.1.0/langchain_agentx_stream_ui_backend.egg-info/dependency_links.txt +1 -0
- langchain-agentx-stream-ui-backend-0.1.0/langchain_agentx_stream_ui_backend.egg-info/requires.txt +7 -0
- langchain-agentx-stream-ui-backend-0.1.0/langchain_agentx_stream_ui_backend.egg-info/top_level.txt +1 -0
- langchain-agentx-stream-ui-backend-0.1.0/pyproject.toml +33 -0
- langchain-agentx-stream-ui-backend-0.1.0/setup.cfg +4 -0
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: langchain-agentx-stream-ui-backend
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: SSE thin-layer SDK: LangchainAgentEvent → standard SSE for langchain_agentx_stream_ui
|
|
5
|
+
Requires-Python: >=3.11
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
Provides-Extra: dev
|
|
8
|
+
|
|
9
|
+
# langchain-agentx-stream-ui-backend
|
|
10
|
+
|
|
11
|
+
SSE 薄层 SDK:**仅 UI 线** — 将 `LangchainAgentEvent`(经 SDK `LangGraphToLangchainAgentEventAdapter` 的 out-of-band / artifact 投影)序列化为前端 SSE 协议。
|
|
12
|
+
|
|
13
|
+
不暴露 LangGraph 原始 dict,也不向浏览器推送 `ToolMessage.content`(model 路径,仅供 LLM)。
|
|
14
|
+
|
|
15
|
+
## 快速接入
|
|
16
|
+
|
|
17
|
+
```python
|
|
18
|
+
from fastapi import FastAPI, Request
|
|
19
|
+
from langchain_agentx_stream_ui_backend import stream_to_sse, wrap_langgraph
|
|
20
|
+
|
|
21
|
+
app = FastAPI()
|
|
22
|
+
|
|
23
|
+
@app.get("/wiki/build")
|
|
24
|
+
async def wiki_build(task_id: str, request: Request):
|
|
25
|
+
graph = build_wiki_workflow(task_id)
|
|
26
|
+
# adapter_config 可省略:wrap_langgraph 内置 DEFAULT_UI_ADAPTER_CONFIG
|
|
27
|
+
events = wrap_langgraph(graph, {"task_id": task_id}, graph_name="WikiBuilder")
|
|
28
|
+
return stream_to_sse(events, request=request, session_id=f"wiki-{task_id}")
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## 测试组织(对齐 T10 金字塔)
|
|
32
|
+
|
|
33
|
+
| 层级 | 位置 | 说明 |
|
|
34
|
+
|------|------|------|
|
|
35
|
+
| L0 单测 | `langchain_agentx_stream_ui_backend/test_*.py`、`tests/test_*.py` | 函数/类级行为 |
|
|
36
|
+
| L1 集成 | `tests/integration/` | FastAPI 路由 + SSE / buffer / permission 全链路 |
|
|
37
|
+
| 冒烟 | `tests/smoke/` | CI 快速回归(`-m smoke`,约 7 项) |
|
|
38
|
+
|
|
39
|
+
原 `examples/` 目录已合并为 `tests/integration/sse/` 下的集成测试。
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
cd backend
|
|
43
|
+
python -m pip install -e ".[dev]"
|
|
44
|
+
|
|
45
|
+
# 全部测试(103 项)
|
|
46
|
+
pytest -v
|
|
47
|
+
|
|
48
|
+
# 仅 L0 单测
|
|
49
|
+
pytest langchain_agentx_stream_ui_backend/ tests/test_*.py -v
|
|
50
|
+
|
|
51
|
+
# 仅 L1 集成测试
|
|
52
|
+
pytest tests/integration/ -v -m integration
|
|
53
|
+
|
|
54
|
+
# 冒烟(发布前 / CI 快速门禁)
|
|
55
|
+
pytest tests/smoke/ -v -m smoke
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### 集成测试覆盖
|
|
59
|
+
|
|
60
|
+
| 模块 | 文件 | 覆盖点 |
|
|
61
|
+
|------|------|--------|
|
|
62
|
+
| Wiki / LangGraph | `test_wiki_workflow_integration.py` | `wrap_langgraph` + SSE |
|
|
63
|
+
| AgentSession | `test_agent_session_integration.py` | `wrap_agent_session` + SSE |
|
|
64
|
+
| EventBuffer | `test_buffer_multi_subscriber_integration.py` | 多订阅、断连不解 ingest |
|
|
65
|
+
| Last-Event-ID | `test_last_event_id_integration.py` | SSE `id:` 续传、400 校验 |
|
|
66
|
+
| Middleware | `test_middleware_http_integration.py` | HTTP 路由级 middleware |
|
|
67
|
+
| Permission raise | `test_permission_raise_integration.py` | ingest 挂起 → POST 回写 → 继续 |
|
|
68
|
+
| 协议不变量 | `test_sse_protocol_integration.py` | meta 字段、bypass/raise 模式 |
|
|
69
|
+
| 断连 | `test_stream_disconnect_integration.py` | `aclose` 2 秒内触发 |
|
|
70
|
+
|
|
71
|
+
## 公开 API
|
|
72
|
+
|
|
73
|
+
- `stream_to_sse(events, *, request, session_id, ...)` → `StreamingResponse`(`SseStreamingService` 门面)
|
|
74
|
+
- `stream_to_sse(..., assign_event_ids=True)` — 形态 A 单连接内 SSE id 行(默认关闭,向后兼容)
|
|
75
|
+
- `stream_to_sse_from_buffer` — 自动读取 `Last-Event-ID` 请求头并写入 SSE `id:` 行
|
|
76
|
+
- `LastEventIdValidator` / `TaggedAgentEvent` — 续传校验与带 id 事件载荷
|
|
77
|
+
- `EventBuffer` / `ingest_stream_to_buffer` — 长任务「POST 触发 ingest + GET 订阅」
|
|
78
|
+
- `stream_to_sse(..., middlewares=[])` — 序列化前可选事件管道(默认空链与 MVP 一致)
|
|
79
|
+
- `permission_mode="raise"` + `create_permission_raise_setup` / `PermissionWaitRegistry` — SDK 权限挂起 → `permission-request` 事件(需 agentx ≥ 0.7.9)
|
|
80
|
+
- `EventMiddleware` / `EventMiddlewarePipeline` / `apply_middlewares` — 脱敏、trace 注入、debug 过滤
|
|
81
|
+
- `wrap_langgraph(graph, input_data, ...)` → `LangGraphEventStream`(内置 `DEFAULT_UI_ADAPTER_CONFIG`)
|
|
82
|
+
- `wrap_agent_session(session_stream, *, cancel=None)` → `AgentSessionEventStream`;`cancel` 为可选 `SessionCancelHandle`
|
|
83
|
+
- `safe_aclose_events` / `EventSourceLifecycle` — 上游释放(断连 finally 使用)
|
|
84
|
+
- `DEFAULT_UI_ADAPTER_CONFIG` / `merge_ui_adapter_config()` — UI 适配器默认(`UiAdapterConfigMerger`)
|
|
85
|
+
- `PROTOCOL_VERSION` — 当前为 `"1"`
|
|
86
|
+
|
|
87
|
+
### AgentSession 取消语义
|
|
88
|
+
|
|
89
|
+
断连或 `stream_to_sse` 结束时,薄层在 `finally` 调用 `events.aclose()`。对 AgentSession 路径:
|
|
90
|
+
|
|
91
|
+
1. 若传入 `cancel=SessionCancelHandle`,`aclose()` **优先** `await cancel.cancel()`
|
|
92
|
+
2. 否则回退 `await session_stream.aclose()`
|
|
93
|
+
|
|
94
|
+
推荐在业务侧传入显式 cancel,以便 SDK 升级时语义稳定:
|
|
95
|
+
|
|
96
|
+
```python
|
|
97
|
+
events = wrap_agent_session(session.astream(question), cancel=session)
|
|
98
|
+
return stream_to_sse(events, request=request)
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### EventMiddleware 示例(tool-result 脱敏)
|
|
102
|
+
|
|
103
|
+
```python
|
|
104
|
+
import dataclasses
|
|
105
|
+
from langchain_agentx.observability.events.langchain_agentx_event_adapter import (
|
|
106
|
+
LangchainAgentEvent,
|
|
107
|
+
LangchainAgentEventType,
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
def redact_tool_result(event: LangchainAgentEvent) -> LangchainAgentEvent | None:
|
|
111
|
+
if event.event_type != LangchainAgentEventType.TOOL_RESULT:
|
|
112
|
+
return event
|
|
113
|
+
data = dict(event.data)
|
|
114
|
+
data["content"] = "[REDACTED]"
|
|
115
|
+
if isinstance(data.get("display"), dict):
|
|
116
|
+
display = dict(data["display"])
|
|
117
|
+
display["value"] = "[REDACTED]"
|
|
118
|
+
data["display"] = display
|
|
119
|
+
return dataclasses.replace(event, data=data)
|
|
120
|
+
|
|
121
|
+
return stream_to_sse(events, request=request, middlewares=[redact_tool_result])
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
middleware 返回 `None` 表示丢弃该事件;抛错则转为 thin_layer `error` 终态帧。
|
|
125
|
+
|
|
126
|
+
### permission_mode="raise"(V2-D,特性开关)
|
|
127
|
+
|
|
128
|
+
默认 `permission_mode="bypass"` 与 MVP 一致。编码类场景可启用 `raise`,将 SDK `PermissionPromptHandler` 挂起转为 documented extension 事件 `permission-request`,等待业务回写后继续。
|
|
129
|
+
|
|
130
|
+
**依赖**:`langchain-agentx >= 0.7.9`(L3 `PermissionPromptHandler`)。
|
|
131
|
+
|
|
132
|
+
```python
|
|
133
|
+
import asyncio
|
|
134
|
+
|
|
135
|
+
from fastapi import FastAPI, HTTPException, Request
|
|
136
|
+
from langchain_agentx_stream_ui_backend import (
|
|
137
|
+
EventBuffer,
|
|
138
|
+
PermissionWaitRegistry,
|
|
139
|
+
create_permission_raise_setup,
|
|
140
|
+
ingest_stream_to_buffer,
|
|
141
|
+
stream_to_sse_from_buffer,
|
|
142
|
+
wrap_langgraph,
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
app = FastAPI()
|
|
146
|
+
_BUFFERS: dict[str, EventBuffer] = {}
|
|
147
|
+
_PERMISSIONS: dict[str, PermissionWaitRegistry] = {}
|
|
148
|
+
|
|
149
|
+
@app.post("/wiki/{task_id}/start")
|
|
150
|
+
async def start(task_id: str):
|
|
151
|
+
perm = create_permission_raise_setup(session_id=task_id)
|
|
152
|
+
_PERMISSIONS[task_id] = perm.registry
|
|
153
|
+
buf = EventBuffer()
|
|
154
|
+
_BUFFERS[task_id] = buf
|
|
155
|
+
graph = build_graph(services={"prompt_handler": perm.handler}) # 注入 agentx graph
|
|
156
|
+
events = wrap_langgraph(graph, {"task_id": task_id}, permission_broker=perm.broker)
|
|
157
|
+
asyncio.create_task(ingest_stream_to_buffer(perm.wrap_stream(events), buf))
|
|
158
|
+
return {"task_id": task_id}
|
|
159
|
+
|
|
160
|
+
@app.post("/wiki/{task_id}/permission")
|
|
161
|
+
async def permission(task_id: str, body: dict):
|
|
162
|
+
registry = _PERMISSIONS[task_id]
|
|
163
|
+
ok = await registry.resolve(body["request_id"], body)
|
|
164
|
+
if not ok:
|
|
165
|
+
raise HTTPException(404)
|
|
166
|
+
return {"ok": True}
|
|
167
|
+
|
|
168
|
+
@app.get("/wiki/{task_id}/stream")
|
|
169
|
+
async def stream(task_id: str, request: Request):
|
|
170
|
+
return stream_to_sse_from_buffer(
|
|
171
|
+
_BUFFERS[task_id],
|
|
172
|
+
request=request,
|
|
173
|
+
permission_mode="raise",
|
|
174
|
+
session_id=task_id,
|
|
175
|
+
)
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
回退至 bypass:不传 `prompt_handler`,`stream_to_sse(..., permission_mode="bypass")`(默认)。
|
|
179
|
+
|
|
180
|
+
## 契约 Fixture
|
|
181
|
+
|
|
182
|
+
SSOT 路径:`backend/tests/fixtures/sse/*.events.json`。生成说明见该目录 `README.md`。
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
# langchain-agentx-stream-ui-backend
|
|
2
|
+
|
|
3
|
+
SSE 薄层 SDK:**仅 UI 线** — 将 `LangchainAgentEvent`(经 SDK `LangGraphToLangchainAgentEventAdapter` 的 out-of-band / artifact 投影)序列化为前端 SSE 协议。
|
|
4
|
+
|
|
5
|
+
不暴露 LangGraph 原始 dict,也不向浏览器推送 `ToolMessage.content`(model 路径,仅供 LLM)。
|
|
6
|
+
|
|
7
|
+
## 快速接入
|
|
8
|
+
|
|
9
|
+
```python
|
|
10
|
+
from fastapi import FastAPI, Request
|
|
11
|
+
from langchain_agentx_stream_ui_backend import stream_to_sse, wrap_langgraph
|
|
12
|
+
|
|
13
|
+
app = FastAPI()
|
|
14
|
+
|
|
15
|
+
@app.get("/wiki/build")
|
|
16
|
+
async def wiki_build(task_id: str, request: Request):
|
|
17
|
+
graph = build_wiki_workflow(task_id)
|
|
18
|
+
# adapter_config 可省略:wrap_langgraph 内置 DEFAULT_UI_ADAPTER_CONFIG
|
|
19
|
+
events = wrap_langgraph(graph, {"task_id": task_id}, graph_name="WikiBuilder")
|
|
20
|
+
return stream_to_sse(events, request=request, session_id=f"wiki-{task_id}")
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## 测试组织(对齐 T10 金字塔)
|
|
24
|
+
|
|
25
|
+
| 层级 | 位置 | 说明 |
|
|
26
|
+
|------|------|------|
|
|
27
|
+
| L0 单测 | `langchain_agentx_stream_ui_backend/test_*.py`、`tests/test_*.py` | 函数/类级行为 |
|
|
28
|
+
| L1 集成 | `tests/integration/` | FastAPI 路由 + SSE / buffer / permission 全链路 |
|
|
29
|
+
| 冒烟 | `tests/smoke/` | CI 快速回归(`-m smoke`,约 7 项) |
|
|
30
|
+
|
|
31
|
+
原 `examples/` 目录已合并为 `tests/integration/sse/` 下的集成测试。
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
cd backend
|
|
35
|
+
python -m pip install -e ".[dev]"
|
|
36
|
+
|
|
37
|
+
# 全部测试(103 项)
|
|
38
|
+
pytest -v
|
|
39
|
+
|
|
40
|
+
# 仅 L0 单测
|
|
41
|
+
pytest langchain_agentx_stream_ui_backend/ tests/test_*.py -v
|
|
42
|
+
|
|
43
|
+
# 仅 L1 集成测试
|
|
44
|
+
pytest tests/integration/ -v -m integration
|
|
45
|
+
|
|
46
|
+
# 冒烟(发布前 / CI 快速门禁)
|
|
47
|
+
pytest tests/smoke/ -v -m smoke
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### 集成测试覆盖
|
|
51
|
+
|
|
52
|
+
| 模块 | 文件 | 覆盖点 |
|
|
53
|
+
|------|------|--------|
|
|
54
|
+
| Wiki / LangGraph | `test_wiki_workflow_integration.py` | `wrap_langgraph` + SSE |
|
|
55
|
+
| AgentSession | `test_agent_session_integration.py` | `wrap_agent_session` + SSE |
|
|
56
|
+
| EventBuffer | `test_buffer_multi_subscriber_integration.py` | 多订阅、断连不解 ingest |
|
|
57
|
+
| Last-Event-ID | `test_last_event_id_integration.py` | SSE `id:` 续传、400 校验 |
|
|
58
|
+
| Middleware | `test_middleware_http_integration.py` | HTTP 路由级 middleware |
|
|
59
|
+
| Permission raise | `test_permission_raise_integration.py` | ingest 挂起 → POST 回写 → 继续 |
|
|
60
|
+
| 协议不变量 | `test_sse_protocol_integration.py` | meta 字段、bypass/raise 模式 |
|
|
61
|
+
| 断连 | `test_stream_disconnect_integration.py` | `aclose` 2 秒内触发 |
|
|
62
|
+
|
|
63
|
+
## 公开 API
|
|
64
|
+
|
|
65
|
+
- `stream_to_sse(events, *, request, session_id, ...)` → `StreamingResponse`(`SseStreamingService` 门面)
|
|
66
|
+
- `stream_to_sse(..., assign_event_ids=True)` — 形态 A 单连接内 SSE id 行(默认关闭,向后兼容)
|
|
67
|
+
- `stream_to_sse_from_buffer` — 自动读取 `Last-Event-ID` 请求头并写入 SSE `id:` 行
|
|
68
|
+
- `LastEventIdValidator` / `TaggedAgentEvent` — 续传校验与带 id 事件载荷
|
|
69
|
+
- `EventBuffer` / `ingest_stream_to_buffer` — 长任务「POST 触发 ingest + GET 订阅」
|
|
70
|
+
- `stream_to_sse(..., middlewares=[])` — 序列化前可选事件管道(默认空链与 MVP 一致)
|
|
71
|
+
- `permission_mode="raise"` + `create_permission_raise_setup` / `PermissionWaitRegistry` — SDK 权限挂起 → `permission-request` 事件(需 agentx ≥ 0.7.9)
|
|
72
|
+
- `EventMiddleware` / `EventMiddlewarePipeline` / `apply_middlewares` — 脱敏、trace 注入、debug 过滤
|
|
73
|
+
- `wrap_langgraph(graph, input_data, ...)` → `LangGraphEventStream`(内置 `DEFAULT_UI_ADAPTER_CONFIG`)
|
|
74
|
+
- `wrap_agent_session(session_stream, *, cancel=None)` → `AgentSessionEventStream`;`cancel` 为可选 `SessionCancelHandle`
|
|
75
|
+
- `safe_aclose_events` / `EventSourceLifecycle` — 上游释放(断连 finally 使用)
|
|
76
|
+
- `DEFAULT_UI_ADAPTER_CONFIG` / `merge_ui_adapter_config()` — UI 适配器默认(`UiAdapterConfigMerger`)
|
|
77
|
+
- `PROTOCOL_VERSION` — 当前为 `"1"`
|
|
78
|
+
|
|
79
|
+
### AgentSession 取消语义
|
|
80
|
+
|
|
81
|
+
断连或 `stream_to_sse` 结束时,薄层在 `finally` 调用 `events.aclose()`。对 AgentSession 路径:
|
|
82
|
+
|
|
83
|
+
1. 若传入 `cancel=SessionCancelHandle`,`aclose()` **优先** `await cancel.cancel()`
|
|
84
|
+
2. 否则回退 `await session_stream.aclose()`
|
|
85
|
+
|
|
86
|
+
推荐在业务侧传入显式 cancel,以便 SDK 升级时语义稳定:
|
|
87
|
+
|
|
88
|
+
```python
|
|
89
|
+
events = wrap_agent_session(session.astream(question), cancel=session)
|
|
90
|
+
return stream_to_sse(events, request=request)
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### EventMiddleware 示例(tool-result 脱敏)
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
import dataclasses
|
|
97
|
+
from langchain_agentx.observability.events.langchain_agentx_event_adapter import (
|
|
98
|
+
LangchainAgentEvent,
|
|
99
|
+
LangchainAgentEventType,
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
def redact_tool_result(event: LangchainAgentEvent) -> LangchainAgentEvent | None:
|
|
103
|
+
if event.event_type != LangchainAgentEventType.TOOL_RESULT:
|
|
104
|
+
return event
|
|
105
|
+
data = dict(event.data)
|
|
106
|
+
data["content"] = "[REDACTED]"
|
|
107
|
+
if isinstance(data.get("display"), dict):
|
|
108
|
+
display = dict(data["display"])
|
|
109
|
+
display["value"] = "[REDACTED]"
|
|
110
|
+
data["display"] = display
|
|
111
|
+
return dataclasses.replace(event, data=data)
|
|
112
|
+
|
|
113
|
+
return stream_to_sse(events, request=request, middlewares=[redact_tool_result])
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
middleware 返回 `None` 表示丢弃该事件;抛错则转为 thin_layer `error` 终态帧。
|
|
117
|
+
|
|
118
|
+
### permission_mode="raise"(V2-D,特性开关)
|
|
119
|
+
|
|
120
|
+
默认 `permission_mode="bypass"` 与 MVP 一致。编码类场景可启用 `raise`,将 SDK `PermissionPromptHandler` 挂起转为 documented extension 事件 `permission-request`,等待业务回写后继续。
|
|
121
|
+
|
|
122
|
+
**依赖**:`langchain-agentx >= 0.7.9`(L3 `PermissionPromptHandler`)。
|
|
123
|
+
|
|
124
|
+
```python
|
|
125
|
+
import asyncio
|
|
126
|
+
|
|
127
|
+
from fastapi import FastAPI, HTTPException, Request
|
|
128
|
+
from langchain_agentx_stream_ui_backend import (
|
|
129
|
+
EventBuffer,
|
|
130
|
+
PermissionWaitRegistry,
|
|
131
|
+
create_permission_raise_setup,
|
|
132
|
+
ingest_stream_to_buffer,
|
|
133
|
+
stream_to_sse_from_buffer,
|
|
134
|
+
wrap_langgraph,
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
app = FastAPI()
|
|
138
|
+
_BUFFERS: dict[str, EventBuffer] = {}
|
|
139
|
+
_PERMISSIONS: dict[str, PermissionWaitRegistry] = {}
|
|
140
|
+
|
|
141
|
+
@app.post("/wiki/{task_id}/start")
|
|
142
|
+
async def start(task_id: str):
|
|
143
|
+
perm = create_permission_raise_setup(session_id=task_id)
|
|
144
|
+
_PERMISSIONS[task_id] = perm.registry
|
|
145
|
+
buf = EventBuffer()
|
|
146
|
+
_BUFFERS[task_id] = buf
|
|
147
|
+
graph = build_graph(services={"prompt_handler": perm.handler}) # 注入 agentx graph
|
|
148
|
+
events = wrap_langgraph(graph, {"task_id": task_id}, permission_broker=perm.broker)
|
|
149
|
+
asyncio.create_task(ingest_stream_to_buffer(perm.wrap_stream(events), buf))
|
|
150
|
+
return {"task_id": task_id}
|
|
151
|
+
|
|
152
|
+
@app.post("/wiki/{task_id}/permission")
|
|
153
|
+
async def permission(task_id: str, body: dict):
|
|
154
|
+
registry = _PERMISSIONS[task_id]
|
|
155
|
+
ok = await registry.resolve(body["request_id"], body)
|
|
156
|
+
if not ok:
|
|
157
|
+
raise HTTPException(404)
|
|
158
|
+
return {"ok": True}
|
|
159
|
+
|
|
160
|
+
@app.get("/wiki/{task_id}/stream")
|
|
161
|
+
async def stream(task_id: str, request: Request):
|
|
162
|
+
return stream_to_sse_from_buffer(
|
|
163
|
+
_BUFFERS[task_id],
|
|
164
|
+
request=request,
|
|
165
|
+
permission_mode="raise",
|
|
166
|
+
session_id=task_id,
|
|
167
|
+
)
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
回退至 bypass:不传 `prompt_handler`,`stream_to_sse(..., permission_mode="bypass")`(默认)。
|
|
171
|
+
|
|
172
|
+
## 契约 Fixture
|
|
173
|
+
|
|
174
|
+
SSOT 路径:`backend/tests/fixtures/sse/*.events.json`。生成说明见该目录 `README.md`。
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"""
|
|
2
|
+
职责:导出薄层 SDK 稳定公开 API。
|
|
3
|
+
链路位置:业务工程 from langchain_agentx_stream_ui_backend import ... 的唯一入口。
|
|
4
|
+
当前裁剪范围:UI-only SSE(LangchainAgentEvent);不含 model 路径 / LangGraph 裸流。
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
__version__ = "0.1.0"
|
|
8
|
+
|
|
9
|
+
from .buffer import (
|
|
10
|
+
BufferIngestService,
|
|
11
|
+
BufferOverflowError,
|
|
12
|
+
BufferStreamingService,
|
|
13
|
+
BufferedEvent,
|
|
14
|
+
EventBuffer,
|
|
15
|
+
ingest_stream_to_buffer,
|
|
16
|
+
stream_to_sse_from_buffer,
|
|
17
|
+
)
|
|
18
|
+
from .buffer_protocol import AbstractEventBuffer
|
|
19
|
+
from .lifecycle import EventSourceLifecycle, safe_aclose_events
|
|
20
|
+
from .middleware import (
|
|
21
|
+
EventMiddleware,
|
|
22
|
+
EventMiddlewarePipeline,
|
|
23
|
+
apply_middlewares,
|
|
24
|
+
)
|
|
25
|
+
from .permission import (
|
|
26
|
+
MIN_SDK_VERSION_FOR_PERMISSION_RAISE,
|
|
27
|
+
PERMISSION_REQUEST_EVENT_TYPE,
|
|
28
|
+
PermissionEventBroker,
|
|
29
|
+
PermissionInterleavedStream,
|
|
30
|
+
PermissionRaiseSetup,
|
|
31
|
+
PermissionResolvePayload,
|
|
32
|
+
PermissionWaitRegistry,
|
|
33
|
+
RaisingPermissionPromptHandler,
|
|
34
|
+
create_permission_raise_setup,
|
|
35
|
+
permission_resolve_payload_to_response,
|
|
36
|
+
prompt_request_to_agent_event,
|
|
37
|
+
)
|
|
38
|
+
from .sse import SseStreamingService, stream_to_sse, TaggedAgentEvent, LastEventIdValidator
|
|
39
|
+
from .ui_adapter import DEFAULT_UI_ADAPTER_CONFIG, merge_ui_adapter_config
|
|
40
|
+
from .versions import PROTOCOL_VERSION
|
|
41
|
+
from .wrap import (
|
|
42
|
+
EventStreamWrapperFactory,
|
|
43
|
+
SessionCancelHandle,
|
|
44
|
+
wrap_agent_session,
|
|
45
|
+
wrap_workflow,
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
__all__ = [
|
|
49
|
+
"AbstractEventBuffer",
|
|
50
|
+
"BufferIngestService",
|
|
51
|
+
"BufferOverflowError",
|
|
52
|
+
"BufferStreamingService",
|
|
53
|
+
"BufferedEvent",
|
|
54
|
+
"DEFAULT_UI_ADAPTER_CONFIG",
|
|
55
|
+
"EventBuffer",
|
|
56
|
+
"EventMiddleware",
|
|
57
|
+
"EventMiddlewarePipeline",
|
|
58
|
+
"MIN_SDK_VERSION_FOR_PERMISSION_RAISE",
|
|
59
|
+
"PERMISSION_REQUEST_EVENT_TYPE",
|
|
60
|
+
"PermissionEventBroker",
|
|
61
|
+
"PermissionInterleavedStream",
|
|
62
|
+
"PermissionRaiseSetup",
|
|
63
|
+
"PermissionResolvePayload",
|
|
64
|
+
"PermissionWaitRegistry",
|
|
65
|
+
"RaisingPermissionPromptHandler",
|
|
66
|
+
"create_permission_raise_setup",
|
|
67
|
+
"EventSourceLifecycle",
|
|
68
|
+
"PROTOCOL_VERSION",
|
|
69
|
+
"SessionCancelHandle",
|
|
70
|
+
"SseStreamingService",
|
|
71
|
+
"LastEventIdValidator",
|
|
72
|
+
"TaggedAgentEvent",
|
|
73
|
+
"permission_resolve_payload_to_response",
|
|
74
|
+
"prompt_request_to_agent_event",
|
|
75
|
+
"ingest_stream_to_buffer",
|
|
76
|
+
"EventStreamWrapperFactory",
|
|
77
|
+
"apply_middlewares",
|
|
78
|
+
"merge_ui_adapter_config",
|
|
79
|
+
"safe_aclose_events",
|
|
80
|
+
"stream_to_sse",
|
|
81
|
+
"stream_to_sse_from_buffer",
|
|
82
|
+
"wrap_agent_session",
|
|
83
|
+
"wrap_workflow",
|
|
84
|
+
]
|