Files
Fu-Jie_openwebui-extensions/plugins/debug/github-copilot-sdk/guides/WORKFLOW_CN.md

26 KiB
Raw Blame 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. 调试日志器:前端控制台日志,用于故障排除

请求处理流程

完整请求生命周期

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)

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. 模型选择

# 输入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)

# 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. 会话创建/恢复

新会话:

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)

恢复会话:

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%)  │  │    │
│  │  └──────────────────────────────────────────┘  │    │
│  └─────────────────────────────────────────────────┘    │
└─────────────────────────────────────────────────────────┘

配置参数

InfiniteSessionConfig(
    enabled=True,                              # 启用无限会话
    background_compaction_threshold=0.8,       # 在 80% token 使用率时开始压缩
    buffer_exhaustion_threshold=0.95           # 95% 紧急阈值
)

行为:

  • < 80%:正常操作,无压缩
  • 80-95%:后台压缩(总结旧消息)
  • > 95%:在下一个请求前强制压缩

流式响应处理

事件驱动架构

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    │
        └────────────────────────┘

流式传输期间的状态管理

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 发出错误通知

事件处理器实现

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 模式合规性

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)

工具执行流程

工具注册

# 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。"
     ▼
流完成

视觉指示器

内容前:

<think>
运行工具generate_random_number...
工具 `generate_random_number` 完成。结果42
</think>

我为你生成了数字 42。

内容开始后:

数字是

> 🔧 **运行工具**`generate_random_number`

> ✅ **工具完成**42

实际上是 42。

系统提示词提取

多源优先级系统

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元数据模型参数

# OpenWebUI 注入模型配置
metadata = body.get("metadata", {})
meta_model = metadata.get("model", {})
meta_params = meta_model.get("params", {})
system_prompt = meta_params.get("system")  # 优先级 1

来源 2模型数据库

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 参数

body_params = body.get("params", {})
system_prompt = body_params.get("system")

来源 4系统消息

for msg in messages:
    if msg.get("role") == "system":
        system_prompt = self._extract_text_from_content(msg.get("content"))
        break

SessionConfig 中的配置

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" 或逗号分隔列表

环境变量

# 由 _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 访问和对象属性访问

故障排除指南

启用调试模式

# 在 OpenWebUI Valves UI 中:
DEBUG = True
SHOW_WORKSPACE_INFO = True
LOG_LEVEL = "debug"

调试输出位置

前端控制台:

// 打开浏览器开发工具 (F12)
// 查找前缀为 [Copilot Pipe] 的日志
console.debug("[Copilot Pipe] 提取的 ChatIDabc123来源__metadata__")

后端日志:

# Python 日志输出
logger.debug(f"[Copilot Pipe] 会话已恢复:{chat_id}")

常见问题

1. 会话未恢复

症状:每次请求都创建新会话
原因

  • chat_id 提取不正确
  • Copilot 端会话过期
  • INFINITE_SESSION=False(会话不持久)

解决方案

# 检查调试日志中的:
"提取的 ChatID<id>(来源:..."
"会话 <id> 未找到(...),正在创建新会话。"

2. 系统提示词未应用

症状:模型忽略配置的系统提示词
原因

  • 在 4 个来源中均未找到
  • 会话已恢复(系统提示词仅在创建时设置)

解决方案

# 检查调试日志中的:
"从 <source> 提取系统提示词长度X"
"配置系统消息模式append"

3. 工具不可用

症状:模型无法使用自定义工具
原因

  • ENABLE_TOOLS=False
  • 工具未在 _initialize_custom_tools 中注册
  • 错误的 AVAILABLE_TOOLS 过滤器

解决方案

# 检查调试日志中的:
"已启用 X 个自定义工具:['tool1', 'tool2']"

性能优化

模型列表缓存

# 第一次请求:从 API 获取
models = await client.list_models()
self._model_cache = [...]  # 缓存结果

# 后续请求:使用缓存
if self._model_cache:
    return self._model_cache

会话持久化

影响:消除每次请求的冗余模型初始化

# 没有会话:
# 每次请求:初始化模型 → 加载上下文 → 生成 → 丢弃

# 有会话chat_id
# 第一次请求:初始化模型 → 加载上下文 → 生成 → 保存
# 后续:恢复 → 生成(即时)

流式 vs 非流式

流式:

  • 降低感知延迟(首个 token 更快)
  • 长响应的更好用户体验
  • 通过生成器退出进行资源清理

非流式:

  • 更简单的错误处理
  • 原子响应(无部分输出)
  • 用于短响应

安全考虑

令牌保护

# ❌ 永远不要记录令牌
logger.debug(f"令牌:{self.valves.GH_TOKEN}")  # 不要这样做

# ✅ 屏蔽敏感数据
logger.debug(f"令牌已配置:{'*' * 10}")

工作区隔离

# 设置 WORKSPACE_DIR 以限制文件访问
WORKSPACE_DIR = "/safe/sandbox/path"

# Copilot CLI 遵守此目录
client_config["cwd"] = WORKSPACE_DIR

输入验证

# 验证 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
  • 增强的工具功能
  • 改进的会话序列化

参考资料


许可证MIT
维护者Fu-Jie (@Fu-Jie)