# 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, # 标签已打开 "content_sent": False # 主内容已开始 } active_tools = {} # 跟踪并发工具执行 ``` **状态转换:** 1. `reasoning_delta` 到达 → `thinking_started = True` → 输出:`\n{reasoning}` 2. `message_delta` 到达 → 如果打开则关闭 `` → `content_sent = True` → 输出:`{content}` 3. `tool.execution_start` → 输出工具指示器(在 `` 内部/外部) 4. `session.complete` → 完成流 --- ## 事件处理机制 ### 事件类型参考 遵循官方 SDK 模式(来自 `copilot.SessionEventType`): | 事件类型 | 描述 | 关键数据字段 | 处理器操作 | |---------|------|-------------|-----------| | `assistant.message_delta` | 主内容流式传输 | `delta_content` | 产出文本块 | | `assistant.reasoning_delta` | 思维链 | `delta_content` | 用 `` 标签包装 | | `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 运行工具:generate_random_number... 工具 `generate_random_number` 完成。结果:42 我为你生成了数字 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` | 在 `` 标签中显示模型推理 | | `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:(来源:...)" "会话 未找到(...),正在创建新会话。" ``` #### 2. 系统提示词未应用 **症状**:模型忽略配置的系统提示词 **原因**: - 在 4 个来源中均未找到 - 会话已恢复(系统提示词仅在创建时设置) **解决方案**: ```python # 检查调试日志中的: "从 提取系统提示词(长度: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))