# 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) ```python # _pipe_impl中 client = await self._get_client(token) ``` ### 步骤2:_get_client函数(line 5523-5561) ```python 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) ```python # _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是会话级别的 ```python # ❓ 如果用户想为不同的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是不同的东西 ```python # CopilotClient = GitHub Copilot SDK客户端 # ProviderConfig = OpenAI/Anthropic等的API配置 # 用户可能混淆: # "怎么传入BYOK的client和provider" # → 实际上只能传provider到session,client是全局的 ``` ### 问题3:BYOK模型混用的情况处理不清楚 ```python # 如果用户想在同一个Pipe中: # - Model A 用 OpenAI API # - Model B 用 Anthropic API # - Model C 用自己的本地LLM # 当前代码是基于全局BYOK配置的,无法为各模型单独设置 ``` --- ## 改进方案 ### 方案A:保持当前架构,只改Provider映射 **思路**:Client保持全局(基于GH_TOKEN),但Provider配置基于模型动态选择 ```python # 在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缓存 ```python 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配置: ```python 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判断: ```python 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 **你需要做的**: 1. 在Valves中配置MODEL_PROVIDER_MAP 2. 在模型选择时读取这个映射 3. 创建session时用对应的provider_config 无需修改Client的创建逻辑!