mirror of
https://github.com/aiclientproxy/proxycast.git
synced 2026-10-13 01:17:22 +08:00
Merge pull request #72 from aiclientproxy/perly6185-lab-main
feat: 添加 X-Provider-Id header 支持实现精确 Provider 路由
This commit is contained in:
@@ -375,12 +375,14 @@ impl Protocol for AnthropicProtocol {
|
||||
config: &AgentConfig,
|
||||
tools: Option<&[Tool]>,
|
||||
tx: mpsc::Sender<StreamEvent>,
|
||||
provider_id: Option<&str>,
|
||||
) -> Result<StreamResult, String> {
|
||||
info!(
|
||||
"[AnthropicProtocol] 发送流式请求: model={}, history_len={}, tools_count={}",
|
||||
"[AnthropicProtocol] 发送流式请求: model={}, history_len={}, tools_count={}, provider_id={:?}",
|
||||
model,
|
||||
messages.len(),
|
||||
tools.map(|t| t.len()).unwrap_or(0)
|
||||
tools.map(|t| t.len()).unwrap_or(0),
|
||||
provider_id
|
||||
);
|
||||
|
||||
let (anthropic_messages, system) =
|
||||
@@ -400,11 +402,18 @@ impl Protocol for AnthropicProtocol {
|
||||
|
||||
let url = format!("{}{}", base_url, self.endpoint());
|
||||
|
||||
let response = client
|
||||
let mut req_builder = client
|
||||
.post(&url)
|
||||
.header("Authorization", format!("Bearer {}", api_key))
|
||||
.header("Content-Type", "application/json")
|
||||
.header("anthropic-version", "2023-06-01")
|
||||
.header("anthropic-version", "2023-06-01");
|
||||
|
||||
// 添加 X-Provider-Id header 用于精确路由
|
||||
if let Some(pid) = provider_id {
|
||||
req_builder = req_builder.header("X-Provider-Id", pid);
|
||||
}
|
||||
|
||||
let response = req_builder
|
||||
.json(&request)
|
||||
.send()
|
||||
.await
|
||||
@@ -435,12 +444,14 @@ impl Protocol for AnthropicProtocol {
|
||||
config: &AgentConfig,
|
||||
tools: Option<&[Tool]>,
|
||||
tx: mpsc::Sender<StreamEvent>,
|
||||
provider_id: Option<&str>,
|
||||
) -> Result<StreamResult, String> {
|
||||
debug!(
|
||||
"[AnthropicProtocol] 继续流式对话: model={}, history_len={}, tools_count={}",
|
||||
"[AnthropicProtocol] 继续流式对话: model={}, history_len={}, tools_count={}, provider_id={:?}",
|
||||
model,
|
||||
messages.len(),
|
||||
tools.map(|t| t.len()).unwrap_or(0)
|
||||
tools.map(|t| t.len()).unwrap_or(0),
|
||||
provider_id
|
||||
);
|
||||
|
||||
let (anthropic_messages, system) = Self::build_messages_from_history(messages, config);
|
||||
@@ -459,11 +470,18 @@ impl Protocol for AnthropicProtocol {
|
||||
|
||||
let url = format!("{}{}", base_url, self.endpoint());
|
||||
|
||||
let response = client
|
||||
let mut req_builder = client
|
||||
.post(&url)
|
||||
.header("Authorization", format!("Bearer {}", api_key))
|
||||
.header("Content-Type", "application/json")
|
||||
.header("anthropic-version", "2023-06-01")
|
||||
.header("anthropic-version", "2023-06-01");
|
||||
|
||||
// 添加 X-Provider-Id header 用于精确路由
|
||||
if let Some(pid) = provider_id {
|
||||
req_builder = req_builder.header("X-Provider-Id", pid);
|
||||
}
|
||||
|
||||
let response = req_builder
|
||||
.json(&request)
|
||||
.send()
|
||||
.await
|
||||
|
||||
@@ -36,6 +36,7 @@ pub trait Protocol: Send + Sync {
|
||||
config: &AgentConfig,
|
||||
tools: Option<&[Tool]>,
|
||||
tx: mpsc::Sender<StreamEvent>,
|
||||
provider_id: Option<&str>,
|
||||
) -> Result<StreamResult, String>;
|
||||
|
||||
/// 继续流式对话(工具调用后)
|
||||
@@ -51,6 +52,7 @@ pub trait Protocol: Send + Sync {
|
||||
config: &AgentConfig,
|
||||
tools: Option<&[Tool]>,
|
||||
tx: mpsc::Sender<StreamEvent>,
|
||||
provider_id: Option<&str>,
|
||||
) -> Result<StreamResult, String>;
|
||||
|
||||
/// 获取 API 端点
|
||||
|
||||
@@ -358,12 +358,14 @@ impl Protocol for OpenAIProtocol {
|
||||
config: &AgentConfig,
|
||||
tools: Option<&[Tool]>,
|
||||
tx: mpsc::Sender<StreamEvent>,
|
||||
provider_id: Option<&str>,
|
||||
) -> Result<StreamResult, String> {
|
||||
info!(
|
||||
"[OpenAIProtocol] 发送流式请求: model={}, history_len={}, tools_count={}",
|
||||
"[OpenAIProtocol] 发送流式请求: model={}, history_len={}, tools_count={}, provider_id={:?}",
|
||||
model,
|
||||
messages.len(),
|
||||
tools.map(|t| t.len()).unwrap_or(0)
|
||||
tools.map(|t| t.len()).unwrap_or(0),
|
||||
provider_id
|
||||
);
|
||||
|
||||
let chat_messages = Self::build_messages(messages, user_message, images, config);
|
||||
@@ -387,14 +389,21 @@ impl Protocol for OpenAIProtocol {
|
||||
let url = format!("{}{}", base_url, self.endpoint());
|
||||
|
||||
eprintln!(
|
||||
"[OpenAIProtocol] 发送请求到: {} model={} stream={}",
|
||||
url, model, request.stream
|
||||
"[OpenAIProtocol] 发送请求到: {} model={} stream={} provider_id={:?}",
|
||||
url, model, request.stream, provider_id
|
||||
);
|
||||
|
||||
let response = client
|
||||
let mut req_builder = client
|
||||
.post(&url)
|
||||
.header("Authorization", format!("Bearer {}", api_key))
|
||||
.header("Content-Type", "application/json")
|
||||
.header("Content-Type", "application/json");
|
||||
|
||||
// 添加 X-Provider-Id header 用于精确路由
|
||||
if let Some(pid) = provider_id {
|
||||
req_builder = req_builder.header("X-Provider-Id", pid);
|
||||
}
|
||||
|
||||
let response = req_builder
|
||||
.json(&request)
|
||||
.send()
|
||||
.await
|
||||
@@ -429,12 +438,14 @@ impl Protocol for OpenAIProtocol {
|
||||
config: &AgentConfig,
|
||||
tools: Option<&[Tool]>,
|
||||
tx: mpsc::Sender<StreamEvent>,
|
||||
provider_id: Option<&str>,
|
||||
) -> Result<StreamResult, String> {
|
||||
debug!(
|
||||
"[OpenAIProtocol] 继续流式对话: model={}, history_len={}, tools_count={}",
|
||||
"[OpenAIProtocol] 继续流式对话: model={}, history_len={}, tools_count={}, provider_id={:?}",
|
||||
model,
|
||||
messages.len(),
|
||||
tools.map(|t| t.len()).unwrap_or(0)
|
||||
tools.map(|t| t.len()).unwrap_or(0),
|
||||
provider_id
|
||||
);
|
||||
|
||||
let chat_messages = Self::build_messages_from_history(messages, config);
|
||||
@@ -457,10 +468,17 @@ impl Protocol for OpenAIProtocol {
|
||||
|
||||
let url = format!("{}{}", base_url, self.endpoint());
|
||||
|
||||
let response = client
|
||||
let mut req_builder = client
|
||||
.post(&url)
|
||||
.header("Authorization", format!("Bearer {}", api_key))
|
||||
.header("Content-Type", "application/json")
|
||||
.header("Content-Type", "application/json");
|
||||
|
||||
// 添加 X-Provider-Id header 用于精确路由
|
||||
if let Some(pid) = provider_id {
|
||||
req_builder = req_builder.header("X-Provider-Id", pid);
|
||||
}
|
||||
|
||||
let response = req_builder
|
||||
.json(&request)
|
||||
.send()
|
||||
.await
|
||||
|
||||
@@ -492,6 +492,53 @@ impl ApiKeyProviderService {
|
||||
.map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
/// 获取下一个可用的 API Key 以及 Provider 信息(按 provider_id 精确查找)
|
||||
/// 用于支持 X-Provider-Id 请求头指定具体的 Provider
|
||||
pub fn get_next_api_key_with_provider_info(
|
||||
&self,
|
||||
db: &DbConnection,
|
||||
provider_id: &str,
|
||||
) -> Result<Option<(String, ApiKeyProvider)>, String> {
|
||||
let conn = db.lock().map_err(|e| e.to_string())?;
|
||||
|
||||
// 获取 Provider 信息
|
||||
let provider = match ApiKeyProviderDao::get_provider_by_id(&conn, provider_id)
|
||||
.map_err(|e| e.to_string())?
|
||||
{
|
||||
Some(p) => p,
|
||||
None => return Ok(None),
|
||||
};
|
||||
|
||||
// 检查 Provider 是否启用
|
||||
if !provider.enabled {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
// 获取该 Provider 的所有启用的 API Keys
|
||||
let keys = ApiKeyProviderDao::get_enabled_api_keys_by_provider(&conn, provider_id)
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
if keys.is_empty() {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
// 获取或创建轮询索引
|
||||
let index = {
|
||||
let mut indices = self.round_robin_index.write().map_err(|e| e.to_string())?;
|
||||
indices
|
||||
.entry(provider_id.to_string())
|
||||
.or_insert_with(|| AtomicUsize::new(0))
|
||||
.fetch_add(1, Ordering::SeqCst)
|
||||
};
|
||||
|
||||
// 选择 API Key
|
||||
let selected_key = &keys[index % keys.len()];
|
||||
|
||||
// 解密并返回
|
||||
let decrypted = self.encryption.decrypt(&selected_key.api_key_encrypted)?;
|
||||
Ok(Some((decrypted, provider)))
|
||||
}
|
||||
|
||||
/// 按 Provider 类型获取下一个可用的 API Key(轮询负载均衡)
|
||||
/// 这个方法会查找所有该类型的 Provider(包括自定义 Provider)
|
||||
pub fn get_next_api_key_by_type(
|
||||
|
||||
@@ -19,7 +19,6 @@ import { ToolsPage } from "./components/tools/ToolsPage";
|
||||
import { AgentChatPage } from "./components/agent";
|
||||
import { PluginUIRenderer } from "./components/plugins/PluginUIRenderer";
|
||||
import { PluginsPage } from "./components/plugins/PluginsPage";
|
||||
import { Toaster } from "./components/ui/sonner";
|
||||
import { flowEventManager } from "./lib/flowEventManager";
|
||||
import { OnboardingWizard, useOnboardingState } from "./components/onboarding";
|
||||
import { ConnectConfirmDialog } from "./components/connect";
|
||||
@@ -209,7 +208,6 @@ function App() {
|
||||
<AppContainer>
|
||||
<AppSidebar currentPage={currentPage} onNavigate={setCurrentPage} />
|
||||
<MainContent>{renderPage()}</MainContent>
|
||||
<Toaster />
|
||||
{/* ProxyCast Connect 确认弹窗 */}
|
||||
{/* _Requirements: 5.2_ */}
|
||||
<ConnectConfirmDialog
|
||||
|
||||
@@ -7,7 +7,7 @@ export function Toaster() {
|
||||
richColors
|
||||
theme="system"
|
||||
toastOptions={{
|
||||
duration: 3000,
|
||||
duration: 5000,
|
||||
classNames: {
|
||||
toast:
|
||||
"group rounded-md border bg-background text-foreground shadow-lg",
|
||||
|
||||
Reference in New Issue
Block a user