- Enable default overwrite installation policy for overlapping skills - Support deep recursive GitHub trees discovery mechanism to resolve #58 - Refactor internal architecture to fully decouple stateless helper logic - READMEs and docs synced (v0.3.0)
9.3 KiB
9.3 KiB
Client传入和管理分析
当前的Client管理架构
┌────────────────────────────────────────┐
│ Pipe Instance (github_copilot_sdk.py) │
│ │
│ _shared_clients = { │
│ "token_hash_1": CopilotClient(...), │ ← 基于GitHub Token缓存
│ "token_hash_2": CopilotClient(...), │
│ } │
└────────────────────────────────────────┘
│
│ await _get_client(token)
│
▼
┌────────────────────────────────────────┐
│ CopilotClient Instance │
│ │
│ [仅需GitHub Token配置] │
│ │
│ config { │
│ github_token: "ghp_...", │
│ cli_path: "...", │
│ config_dir: "...", │
│ env: {...}, │
│ cwd: "..." │
│ } │
└────────────────────────────────────────┘
│
│ create_session(session_config)
│
▼
┌────────────────────────────────────────┐
│ Session (per-session configuration) │
│ │
│ session_config { │
│ model: "real_model_id", │
│ provider: { │ ← ⭐ BYOK配置在这里
│ type: "openai", │
│ base_url: "https://api.openai...",
│ api_key: "sk-...", │
│ ... │
│ }, │
│ infinite_sessions: {...}, │
│ system_message: {...}, │
│ ... │
│ } │
└────────────────────────────────────────┘
目前的流程(代码实际位置)
步骤1:获取或创建Client(line 6208)
# _pipe_impl中
client = await self._get_client(token)
步骤2:_get_client函数(line 5523-5561)
async def _get_client(self, token: str) -> Any:
"""Get or create the persistent CopilotClient from the pool based on token."""
if not token:
raise ValueError("GitHub Token is required to initialize CopilotClient")
token_hash = hashlib.md5(token.encode()).hexdigest()
# 查看是否已有缓存的client
client = self.__class__._shared_clients.get(token_hash)
if client and client状态正常:
return client # ← 复用已有的client
# 否则创建新client
client_config = self._build_client_config(user_id=None, chat_id=None)
client_config["github_token"] = token
new_client = CopilotClient(client_config)
await new_client.start()
self.__class__._shared_clients[token_hash] = new_client
return new_client
步骤3:创建会话时传入provider(line 6253-6270)
# _pipe_impl中,BYOK部分
if is_byok_model:
provider_config = {
"type": byok_type, # "openai" or "anthropic"
"wire_api": byok_wire_api,
"base_url": byok_base_url,
"api_key": byok_api_key or None,
"bearer_token": byok_bearer_token or None,
}
# 然后传入session config
session = await client.create_session(config={
"model": real_model_id,
"provider": provider_config, # ← provider在这里传给session
...
})
关键问题:架构的2个层级
| 层级 | 用途 | 配置内容 | 缓存方式 |
|---|---|---|---|
| CopilotClient | CLI和运行时底层逻辑 | GitHub Token, CLI path, 环境变量 | 基于token_hash全局缓存 |
| Session | 具体的对话会话 | Model, Provider(BYOK), Tools, System Prompt | 不缓存(每次新建) |
当前的问题
问题1:Client是全局缓存的,但Provider是会话级别的
# ❓ 如果用户想为不同的BYOK模型使用不同的Client呢?
# 当前无法做到,因为Client基于token缓存是全局的
# 例子:
# Client A: OpenAI API key (token_hash_1)
# Client B: Anthropic API key (token_hash_2)
# 但在Pipe中,只有一个GH_TOKEN,导致只能有一个Client
问题2:Provider和Client是不同的东西
# CopilotClient = GitHub Copilot SDK客户端
# ProviderConfig = OpenAI/Anthropic等的API配置
# 用户可能混淆:
# "怎么传入BYOK的client和provider"
# → 实际上只能传provider到session,client是全局的
问题3:BYOK模型混用的情况处理不清楚
# 如果用户想在同一个Pipe中:
# - Model A 用 OpenAI API
# - Model B 用 Anthropic API
# - Model C 用自己的本地LLM
# 当前代码是基于全局BYOK配置的,无法为各模型单独设置
改进方案
方案A:保持当前架构,只改Provider映射
思路:Client保持全局(基于GH_TOKEN),但Provider配置基于模型动态选择
# 在Valves中添加
class Valves(BaseModel):
# ... 现有配置 ...
# 新增:模型到Provider的映射 (JSON)
MODEL_PROVIDER_MAP: str = Field(
default="{}",
description='Map model IDs to BYOK providers (JSON). Example: '
'{"gpt-4": {"type": "openai", "base_url": "...", "api_key": "..."}, '
'"claude-3": {"type": "anthropic", "base_url": "...", "api_key": "..."}}'
)
# 在_pipe_impl中
def _get_provider_config(self, model_id: str, byok_active: bool) -> Optional[dict]:
"""Get provider config for a specific model"""
if not byok_active:
return None
try:
model_map = json.loads(self.valves.MODEL_PROVIDER_MAP or "{}")
return model_map.get(model_id)
except:
return None
# 使用时
provider_config = self._get_provider_config(real_model_id, byok_active) or {
"type": byok_type,
"base_url": byok_base_url,
"api_key": byok_api_key,
...
}
优点:最小改动,复用现有Client架构
缺点:多个BYOK模型仍共享一个Client(只要GH_TOKEN相同)
方案B:为不同BYOK提供商创建不同的Client
思路:扩展_get_client,支持基于provider_type的多client缓存
async def _get_or_create_client(
self,
token: str,
provider_type: str = "github" # "github", "openai", "anthropic"
) -> Any:
"""Get or create client based on token and provider type"""
if provider_type == "github" or not provider_type:
# 现有逻辑
token_hash = hashlib.md5(token.encode()).hexdigest()
else:
# 为BYOK提供商创建不同的client
composite_key = f"{token}:{provider_type}"
token_hash = hashlib.md5(composite_key.encode()).hexdigest()
# 从缓存获取或创建
...
优点:隔离不同BYOK提供商的Client
缺点:更复杂,需要更多改动
建议的改进路线
优先级1(高):方案A - 模型到Provider的映射
添加Valves配置:
MODEL_PROVIDER_MAP: str = Field(
default="{}",
description='Map specific models to their BYOK providers (JSON format)'
)
使用方式:
{
"gpt-4": {
"type": "openai",
"base_url": "https://api.openai.com/v1",
"api_key": "sk-..."
},
"claude-3": {
"type": "anthropic",
"base_url": "https://api.anthropic.com/v1",
"api_key": "ant-..."
},
"llama-2": {
"type": "openai", # 开源模型通常使用openai兼容API
"base_url": "http://localhost:8000/v1",
"api_key": "sk-local"
}
}
优先级2(中):在_build_session_config中考虑provider_config
修改infinite_session初始化,基于provider_config判断:
def _build_session_config(..., provider_config=None):
# 如果使用了BYOK provider,需要特殊处理infinite_session
infinite_session_config = None
if self.valves.INFINITE_SESSION and provider_config is None:
# 仅官方Copilot模型启用compression
infinite_session_config = InfiniteSessionConfig(...)
优先级3(低):方案B - 多client缓存(长期改进)
如果需要完全隔离不同BYOK提供商的Client。
总结:如果你要传入BYOK client
现状:
- CopilotClient是基于GH_TOKEN全局缓存的
- Provider配置是在SessionConfig级别动态设置的
- 一个Client可以创建多个Session,每个Session用不同的Provider
改进后:
- 添加MODEL_PROVIDER_MAP配置
- 对每个模型的请求,动态选择对应的Provider配置
- 同一个Client可以为不同Provider服务不同的models
你需要做的:
- 在Valves中配置MODEL_PROVIDER_MAP
- 在模型选择时读取这个映射
- 创建session时用对应的provider_config
无需修改Client的创建逻辑!