Files
Fu-Jie_openwebui-extensions/plugins/debug/legacy_docs/guides/WORKFLOW_CN.md

836 lines
26 KiB
Markdown
Raw Normal View History

# GitHub Copilot SDK 集成工作流程
**作者:** Fu-Jie
**版本:** 0.2.3
**最后更新:** 2026-01-27
---
## 目录
1. [架构概览](#架构概览)
2. [请求处理流程](#请求处理流程)
3. [会话管理](#会话管理)
4. [流式响应处理](#流式响应处理)
5. [事件处理机制](#事件处理机制)
6. [工具执行流程](#工具执行流程)
7. [系统提示词提取](#系统提示词提取)
8. [配置参数](#配置参数)
9. [核心函数参考](#核心函数参考)
---
## 架构概览
### 组件图
```
┌─────────────────────────────────────────────────────────────┐
│ OpenWebUI │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ Pipe 接口 (入口点) │ │
│ └─────────────────────┬─────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ _pipe_impl (主逻辑) │ │
│ │ ┌──────────────────────────────────────────────────┐ │ │
│ │ │ 1. 环境设置 (_setup_env) │ │ │
│ │ │ 2. 模型选择 (request_model 解析) │ │ │
│ │ │ 3. 聊天上下文提取 │ │ │
│ │ │ 4. 系统提示词提取 │ │ │
│ │ │ 5. 会话管理 (创建/恢复) │ │ │
│ │ │ 6. 流式/非流式响应 │ │ │
│ │ └──────────────────────────────────────────────────┘ │ │
│ └─────────────────────┬─────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ GitHub Copilot 客户端 │ │
│ │ ┌──────────────────────────────────────────────────┐ │ │
│ │ │ • CopilotClient (SDK 实例) │ │ │
│ │ │ • Session (对话上下文) │ │ │
│ │ │ • Event Stream (异步事件流) │ │ │
│ │ └──────────────────────────────────────────────────┘ │ │
│ └─────────────────────┬─────────────────────────────────┘ │
│ │ │
└────────────────────────┼─────────────────────────────────────┘
┌──────────────────────┐
│ Copilot CLI 进程 │
│ (后端代理) │
└──────────────────────┘
```
### 核心组件
1. **Pipe 接口**OpenWebUI 的标准入口点
2. **环境管理器**CLI 设置、令牌验证、环境变量
3. **会话管理器**:持久化对话状态,自动压缩
4. **事件处理器**:异步流式事件处理器
5. **工具系统**:自定义工具注册和执行
6. **调试日志器**:前端控制台日志,用于故障排除
---
## 请求处理流程
### 完整请求生命周期
```mermaid
graph TD
A[OpenWebUI 请求] --> B[pipe 入口点]
B --> C[_pipe_impl]
C --> D{设置环境}
D --> E[解析模型 ID]
E --> F[提取聊天上下文]
F --> G[提取系统提示词]
G --> H{会话存在?}
H -->|是| I[恢复会话]
H -->|否| J[创建新会话]
I --> K[初始化工具]
J --> K
K --> L[处理图片]
L --> M{流式模式?}
M -->|是| N[stream_response]
M -->|否| O[send_and_wait]
N --> P[异步事件流]
O --> Q[直接响应]
P --> R[返回到 OpenWebUI]
Q --> R
```
### 逐步分解
#### 1. 环境设置 (`_setup_env`)
```python
def _setup_env(self, __event_call__=None):
"""
优先级:
1. 检查 VALVES.CLI_PATH
2. 搜索系统 PATH
3. 自动通过 curl 安装(如果未找到)
4. 设置 GH_TOKEN 环境变量
"""
```
**操作:**
- 定位 Copilot CLI 二进制文件
- 设置 `COPILOT_CLI_PATH` 环境变量
- 配置 `GH_TOKEN` 进行身份验证
- 应用自定义环境变量
#### 2. 模型选择
```python
# 输入body["model"] = "copilotsdk-claude-sonnet-4.5"
request_model = body.get("model", "")
if request_model.startswith(f"{self.id}-"):
real_model_id = request_model[len(f"{self.id}-"):] # "claude-sonnet-4.5"
```
#### 3. 聊天上下文提取 (`_get_chat_context`)
```python
# chat_id 的优先级顺序:
# 1. __metadata__最可靠
# 2. body["chat_id"]
# 3. body["metadata"]["chat_id"]
chat_ctx = self._get_chat_context(body, __metadata__, __event_call__)
chat_id = chat_ctx.get("chat_id")
```
#### 4. 系统提示词提取 (`_extract_system_prompt`)
多源回退策略:
1. `metadata.model.params.system`
2. 模型数据库查询(按 model_id
3. `body.params.system`
4. 包含 `role="system"` 的消息
#### 5. 会话创建/恢复
**新会话:**
```python
session_config = SessionConfig(
session_id=chat_id,
model=real_model_id,
streaming=is_streaming,
tools=custom_tools,
system_message={"mode": "append", "content": system_prompt_content},
infinite_sessions=InfiniteSessionConfig(
enabled=True,
background_compaction_threshold=0.8,
buffer_exhaustion_threshold=0.95
)
)
session = await client.create_session(config=session_config)
```
**恢复会话:**
```python
try:
session = await client.resume_session(chat_id)
# 会话状态保留:历史、工具、工作区
except Exception:
# 回退到创建新会话
```
---
## 会话管理
### 无限会话架构
```
┌─────────────────────────────────────────────────────────┐
│ 会话生命周期 │
│ │
│ ┌──────────┐ 创建 ┌──────────┐ 恢复 ┌───────────┐ │
│ │ Chat ID │─────▶ │ Session │ ◀────────│ OpenWebUI │ │
│ └──────────┘ │ State │ └───────────┘ │
│ └─────┬────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────┐ │
│ │ 上下文窗口管理 │ │
│ │ ┌──────────────────────────────────────────┐ │ │
│ │ │ 消息 [user, assistant, tool_results...] │ │ │
│ │ │ Token 使用率: ████████████░░░░ (80%) │ │ │
│ │ └──────────────────────────────────────────┘ │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ ┌──────────────────────────────────────────┐ │ │
│ │ │ 达到阈值 (0.8) │ │ │
│ │ │ → 后台压缩触发 │ │ │
│ │ └──────────────────────────────────────────┘ │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ ┌──────────────────────────────────────────┐ │ │
│ │ │ 压缩摘要 + 最近消息 │ │ │
│ │ │ Token 使用率: ██████░░░░░░░░░░░ (40%) │ │ │
│ │ └──────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
```
### 配置参数
```python
InfiniteSessionConfig(
enabled=True, # 启用无限会话
background_compaction_threshold=0.8, # 在 80% token 使用率时开始压缩
buffer_exhaustion_threshold=0.95 # 95% 紧急阈值
)
```
**行为:**
- **< 80%**:正常操作,无压缩
- **80-95%**:后台压缩(总结旧消息)
- **> 95%**:在下一个请求前强制压缩
---
## 流式响应处理
### 事件驱动架构
```python
async def stream_response(
self, client, session, send_payload, init_message: str = "", __event_call__=None
) -> AsyncGenerator:
"""
使用基于队列的缓冲进行异步事件处理。
流程:
1. 启动异步发送任务
2. 注册事件处理器
3. 通过队列处理事件
4. 向 OpenWebUI 产出块
5. 清理资源
"""
```
### 事件处理管道
```
┌────────────────────────────────────────────────────────────┐
│ Copilot SDK 事件流 │
└────────────────────┬───────────────────────────────────────┘
┌────────────────────────┐
│ 事件处理器 │
│ (同步回调) │
└────────┬───────────────┘
┌────────────────────────┐
│ 异步队列 │
│ (线程安全) │
└────────┬───────────────┘
┌────────────────────────┐
│ 消费者循环 │
│ (async for) │
└────────┬───────────────┘
┌────────────────────────┐
│ yield 到 OpenWebUI │
└────────────────────────┘
```
### 流式传输期间的状态管理
```python
state = {
"thinking_started": False, # <think> 标签已打开
"content_sent": False # 主内容已开始
}
active_tools = {} # 跟踪并发工具执行
```
**状态转换:**
1. `reasoning_delta` 到达 → `thinking_started = True` → 输出:`<think>\n{reasoning}`
2. `message_delta` 到达 → 如果打开则关闭 `</think>``content_sent = True` → 输出:`{content}`
3. `tool.execution_start` → 输出工具指示器(在 `<think>` 内部/外部)
4. `session.complete` → 完成流
---
## 事件处理机制
### 事件类型参考
遵循官方 SDK 模式(来自 `copilot.SessionEventType`
| 事件类型 | 描述 | 关键数据字段 | 处理器操作 |
|---------|------|-------------|-----------|
| `assistant.message_delta` | 主内容流式传输 | `delta_content` | 产出文本块 |
| `assistant.reasoning_delta` | 思维链 | `delta_content` | 用 `<think>` 标签包装 |
| `tool.execution_start` | 工具调用启动 | `name`, `tool_call_id` | 显示工具指示器 |
| `tool.execution_complete` | 工具完成 | `result.content` | 显示完成状态 |
| `session.compaction_start` | 上下文压缩开始 | - | 记录调试信息 |
| `session.compaction_complete` | 压缩完成 | - | 记录调试信息 |
| `session.error` | 发生错误 | `error`, `message` | 发出错误通知 |
### 事件处理器实现
```python
def handler(event):
"""遵循官方 SDK 模式处理流式事件。"""
event_type = get_event_type(event) # 处理枚举/字符串类型
# 使用 safe_get_data_attr 提取数据(处理 dict/object
if event_type == "assistant.message_delta":
delta = safe_get_data_attr(event, "delta_content")
if delta:
queue.put_nowait(delta) # 线程安全入队
```
### 官方 SDK 模式合规性
```python
def safe_get_data_attr(event, attr: str, default=None):
"""
官方模式event.data.delta_content
处理 dict 和对象访问模式。
"""
if not hasattr(event, "data") or event.data is None:
return default
data = event.data
# Dict 访问(类似 JSON
if isinstance(data, dict):
return data.get(attr, default)
# 对象属性Python SDK
return getattr(data, attr, default)
```
---
## 工具执行流程
### 工具注册
```python
# 1. 在模块级别定义工具
@define_tool(description="在指定范围内生成随机整数。")
async def generate_random_number(params: RandomNumberParams) -> str:
number = random.randint(params.min, params.max)
return f"生成的随机数: {number}"
# 2. 在 _initialize_custom_tools 中注册
def _initialize_custom_tools(self):
if not self.valves.ENABLE_TOOLS:
return []
all_tools = {
"generate_random_number": generate_random_number,
}
# 根据 AVAILABLE_TOOLS valve 过滤
if self.valves.AVAILABLE_TOOLS == "all":
return list(all_tools.values())
enabled = [t.strip() for t in self.valves.AVAILABLE_TOOLS.split(",")]
return [all_tools[name] for name in enabled if name in all_tools]
```
### 工具执行时间线
```
用户消息:生成一个 1 到 100 之间的随机数
模型决策:使用工具 `generate_random_number`
事件tool.execution_start
│ → 显示:"🔧 运行工具generate_random_number"
工具函数执行(异步)
事件tool.execution_complete
│ → 结果:"生成的随机数42"
│ → 显示:"✅ 工具完成42"
模型使用工具结果生成响应
事件assistant.message_delta
│ → "我为你生成了数字 42。"
流完成
```
### 视觉指示器
**内容前:**
```markdown
<think>
运行工具generate_random_number...
工具 `generate_random_number` 完成。结果42
</think>
我为你生成了数字 42。
```
**内容开始后:**
```markdown
数字是
> 🔧 **运行工具**`generate_random_number`
> ✅ **工具完成**42
实际上是 42。
```
---
## 系统提示词提取
### 多源优先级系统
```python
async def _extract_system_prompt(self, body, messages, request_model, real_model_id):
"""
优先级顺序:
1. metadata.model.params.system最高
2. 模型数据库查询
3. body.params.system
4. messages[role="system"](回退)
"""
```
### 来源 1元数据模型参数
```python
# OpenWebUI 注入模型配置
metadata = body.get("metadata", {})
meta_model = metadata.get("model", {})
meta_params = meta_model.get("params", {})
system_prompt = meta_params.get("system") # 优先级 1
```
### 来源 2模型数据库
```python
from open_webui.models.models import Models
# 尝试多个模型 ID 变体
model_ids_to_try = [
request_model, # "copilotsdk-claude-sonnet-4.5"
request_model.removeprefix(...), # "claude-sonnet-4.5"
real_model_id, # 来自 valves
]
for mid in model_ids_to_try:
model_record = Models.get_model_by_id(mid)
if model_record and hasattr(model_record, "params"):
system_prompt = model_record.params.get("system")
if system_prompt:
break
```
### 来源 3Body 参数
```python
body_params = body.get("params", {})
system_prompt = body_params.get("system")
```
### 来源 4系统消息
```python
for msg in messages:
if msg.get("role") == "system":
system_prompt = self._extract_text_from_content(msg.get("content"))
break
```
### SessionConfig 中的配置
```python
system_message_config = {
"mode": "append", # 追加到对话上下文
"content": system_prompt_content
}
session_config = SessionConfig(
system_message=system_message_config,
# ... 其他参数
)
```
---
## 配置参数
### Valve 定义
| 参数 | 类型 | 默认值 | 描述 |
|-----|------|--------|------|
| `GH_TOKEN` | str | `""` | GitHub 精细化令牌(需要 'Copilot Requests' 权限) |
| `MODEL_ID` | str | `"claude-sonnet-4.5"` | 动态获取失败时的默认模型 |
| `CLI_PATH` | str | `"/usr/local/bin/copilot"` | Copilot CLI 二进制文件路径 |
| `DEBUG` | bool | `False` | 启用前端控制台调试日志 |
| `LOG_LEVEL` | str | `"error"` | CLI 日志级别none、error、warning、info、debug、all |
| `SHOW_THINKING` | bool | `True` | 在 `<think>` 标签中显示模型推理 |
| `SHOW_WORKSPACE_INFO` | bool | `True` | 在调试模式下显示会话工作区路径 |
| `EXCLUDE_KEYWORDS` | str | `""` | 逗号分隔的关键字,用于排除模型 |
| `WORKSPACE_DIR` | str | `""` | 限制的工作区目录(空 = 进程 cwd |
| `INFINITE_SESSION` | bool | `True` | 启用自动上下文压缩 |
| `COMPACTION_THRESHOLD` | float | `0.8` | 80% token 使用率时后台压缩 |
| `BUFFER_THRESHOLD` | float | `0.95` | 95% 紧急阈值 |
| `TIMEOUT` | int | `300` | 流块超时(秒) |
| `CUSTOM_ENV_VARS` | str | `""` | 自定义环境变量的 JSON 字符串 |
| `ENABLE_TOOLS` | bool | `False` | 启用自定义工具系统 |
| `AVAILABLE_TOOLS` | str | `"all"` | 可用工具:"all" 或逗号分隔列表 |
### 环境变量
```bash
# 由 _setup_env 设置
export COPILOT_CLI_PATH="/usr/local/bin/copilot"
export GH_TOKEN="ghp_xxxxxxxxxxxxxxxxxxxx"
export GITHUB_TOKEN="ghp_xxxxxxxxxxxxxxxxxxxx"
# 自定义变量(来自 CUSTOM_ENV_VARS valve
export CUSTOM_VAR_1="value1"
export CUSTOM_VAR_2="value2"
```
---
## 核心函数参考
### 入口点
#### `pipe(body, __metadata__, __event_emitter__, __event_call__)`
- **目的**OpenWebUI 稳定入口点
- **返回**:委托给 `_pipe_impl`
#### `_pipe_impl(body, __metadata__, __event_emitter__, __event_call__)`
- **目的**:主请求处理逻辑
- **流程**:设置 → 提取 → 会话 → 响应
- **返回**`str`(非流式)或 `AsyncGenerator`(流式)
#### `pipes()`
- **目的**:动态模型列表获取
- **返回**:带有倍数信息的可用模型列表
- **缓存**:使用 `_model_cache` 避免重复 API 调用
### 会话管理
#### `_build_session_config(chat_id, real_model_id, custom_tools, system_prompt_content, is_streaming)`
- **目的**:构建 SessionConfig 对象
- **返回**:带有无限会话和工具的 `SessionConfig`
#### `_get_chat_context(body, __metadata__, __event_call__)`
- **目的**:使用优先级回退提取 chat_id
- **返回**`{"chat_id": str}`
### 流式传输
#### `stream_response(client, session, send_payload, init_message, __event_call__)`
- **目的**:异步流式事件处理器
- **产出**:文本块到 OpenWebUI
- **资源**:自动清理客户端和会话
#### `handler(event)`
- **目的**:同步事件回调(在 `stream_response` 内)
- **操作**:解析事件 → 入队块 → 更新状态
### 辅助函数
#### `_emit_debug_log(message, __event_call__)`
- **目的**:将调试日志发送到前端控制台
- **条件**:仅当 `DEBUG=True`
#### `_setup_env(__event_call__)`
- **目的**:定位 CLI设置环境变量
- **副作用**:修改 `os.environ`
#### `_extract_system_prompt(body, messages, request_model, real_model_id, __event_call__)`
- **目的**:多源系统提示词提取
- **返回**`(system_prompt_content, source_name)`
#### `_process_images(messages, __event_call__)`
- **目的**:从多模态消息中提取文本和图片
- **返回**`(text_content, attachments_list)`
#### `_initialize_custom_tools()`
- **目的**:注册和过滤自定义工具
- **返回**:工具函数列表
### 实用函数
#### `get_event_type(event) -> str`
- **目的**:从枚举/字符串提取事件类型字符串
- **处理**`SessionEventType` 枚举 → `.value` 提取
#### `safe_get_data_attr(event, attr: str, default=None)`
- **目的**:从 event.data 安全提取属性
- **处理**dict 访问和对象属性访问
---
## 故障排除指南
### 启用调试模式
```python
# 在 OpenWebUI Valves UI 中:
DEBUG = True
SHOW_WORKSPACE_INFO = True
LOG_LEVEL = "debug"
```
### 调试输出位置
**前端控制台:**
```javascript
// 打开浏览器开发工具 (F12)
// 查找前缀为 [Copilot Pipe] 的日志
console.debug("[Copilot Pipe] 提取的 ChatIDabc123来源__metadata__")
```
**后端日志:**
```python
# Python 日志输出
logger.debug(f"[Copilot Pipe] 会话已恢复:{chat_id}")
```
### 常见问题
#### 1. 会话未恢复
**症状**:每次请求都创建新会话
**原因**
- `chat_id` 提取不正确
- Copilot 端会话过期
- `INFINITE_SESSION=False`(会话不持久)
**解决方案**
```python
# 检查调试日志中的:
"提取的 ChatID<id>(来源:..."
"会话 <id> 未找到(...),正在创建新会话。"
```
#### 2. 系统提示词未应用
**症状**:模型忽略配置的系统提示词
**原因**
- 在 4 个来源中均未找到
- 会话已恢复(系统提示词仅在创建时设置)
**解决方案**
```python
# 检查调试日志中的:
"从 <source> 提取系统提示词长度X"
"配置系统消息模式append"
```
#### 3. 工具不可用
**症状**:模型无法使用自定义工具
**原因**
- `ENABLE_TOOLS=False`
- 工具未在 `_initialize_custom_tools` 中注册
- 错误的 `AVAILABLE_TOOLS` 过滤器
**解决方案**
```python
# 检查调试日志中的:
"已启用 X 个自定义工具:['tool1', 'tool2']"
```
---
## 性能优化
### 模型列表缓存
```python
# 第一次请求:从 API 获取
models = await client.list_models()
self._model_cache = [...] # 缓存结果
# 后续请求:使用缓存
if self._model_cache:
return self._model_cache
```
### 会话持久化
**影响**:消除每次请求的冗余模型初始化
```python
# 没有会话:
# 每次请求:初始化模型 → 加载上下文 → 生成 → 丢弃
# 有会话chat_id
# 第一次请求:初始化模型 → 加载上下文 → 生成 → 保存
# 后续:恢复 → 生成(即时)
```
### 流式 vs 非流式
**流式:**
- 降低感知延迟(首个 token 更快)
- 长响应的更好用户体验
- 通过生成器退出进行资源清理
**非流式:**
- 更简单的错误处理
- 原子响应(无部分输出)
- 用于短响应
---
## 安全考虑
### 令牌保护
```python
# ❌ 永远不要记录令牌
logger.debug(f"令牌:{self.valves.GH_TOKEN}") # 不要这样做
# ✅ 屏蔽敏感数据
logger.debug(f"令牌已配置:{'*' * 10}")
```
### 工作区隔离
```python
# 设置 WORKSPACE_DIR 以限制文件访问
WORKSPACE_DIR = "/safe/sandbox/path"
# Copilot CLI 遵守此目录
client_config["cwd"] = WORKSPACE_DIR
```
### 输入验证
```python
# 验证 chat_id 格式
if chat_id and not re.match(r'^[a-zA-Z0-9_-]+$', chat_id):
logger.warning(f"无效的 chat_id 格式:{chat_id}")
chat_id = None
```
---
## 未来增强
### 计划功能
1. **多会话管理**:支持每个用户的多个并行会话
2. **会话分析**:跟踪 token 使用率、压缩频率
3. **工具结果缓存**:避免冗余工具调用
4. **自定义事件过滤器**:用户可配置的事件处理
5. **工作区模板**:预配置的工作区环境
6. **流式中止**:优雅取消长时间运行的请求
### API 演进
监控 Copilot SDK 更新:
- 新事件类型(例如 `assistant.function_call`
- 增强的工具功能
- 改进的会话序列化
---
## 参考资料
- [GitHub Copilot SDK 文档](https://github.com/github/copilot-sdk)
- [OpenWebUI Pipe 开发](https://docs.openwebui.com/)
- [OpenWebUI Extensions 项目](https://github.com/Fu-Jie/openwebui-extensions)
---
**许可证**MIT
**维护者**Fu-Jie ([@Fu-Jie](https://github.com/Fu-Jie))