2026-01-28 02:14:30 +08:00
|
|
|
|
# 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/)
|
2026-02-13 11:10:48 +08:00
|
|
|
|
- [OpenWebUI Extensions 项目](https://github.com/Fu-Jie/openwebui-extensions)
|
2026-01-28 02:14:30 +08:00
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
**许可证**:MIT
|
|
|
|
|
|
**维护者**:Fu-Jie ([@Fu-Jie](https://github.com/Fu-Jie))
|