# 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
```

### 来源 3：Body 参数

```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] 提取的 ChatID：abc123（来源：__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/)
- [Awesome OpenWebUI 项目](https://github.com/Fu-Jie/awesome-openwebui)

---

**许可证**：MIT  
**维护者**：Fu-Jie ([@Fu-Jie](https://github.com/Fu-Jie))
