Files

1 line
24 KiB
JSON

{"repo": "bestlovelygod/vegaagent", "files": {"src/Agent.Host/Endpoints/LlmConfigEndpoints.cs": {"4282634f0ea01f6da07a9989ae48a73a60c3f2a0": {"t": 1785746214.3578598, "n": 11418, "h": "fc7946947e486cf9", "c": "// ============================================================================\n// LLM 配置管理 API — 多提供商支持\n// ============================================================================\n\nusing Agent.Core.Models;\nusing System.Text.Json;\n\nnamespace Agent.Host.Endpoints;\n\npublic static class LlmConfigEndpoints\n{\n private static readonly string ConfigFilePath = Path.GetFullPath(\n Path.Combine(AppContext.BaseDirectory, \"..\", \"..\", \"..\", \"..\", \"..\", \"data\", \"llm-config.json\"));\n\n private static readonly JsonSerializerOptions JsonOptions = new()\n {\n WriteIndented = true,\n PropertyNamingPolicy = JsonNamingPolicy.CamelCase\n };\n\n public static void MapLlmConfigEndpoints(this WebApplication app)\n {\n var group = app.MapGroup(\"/api/config/llm\")\n .WithTags(\"LLM Config\")\n .WithDescription(\"LLM 配置管理\");\n\n // GET /api/config/llm — 获取 LLM 配置(API Key 脱敏)\n group.MapGet(\"/\", async () =>\n {\n var config = await LoadConfigAsync();\n var maskedConfig = MaskConfig(config);\n return Results.Ok(maskedConfig);\n })\n .WithName(\"GetLlmConfig\")\n .WithDescription(\"获取 LLM 配置(API Key 脱敏)\");\n\n // PUT /api/config/llm/provider — 切换当前 API 提供商\n group.MapPut(\"/provider\", async (SwitchProviderRequest request) =>\n {\n var config = await LoadConfigAsync();\n \n if (!config.Providers.ContainsKey(request.Provider))\n return Results.BadRequest(new { error = $\"提供商 '{request.Provider}' 不存在\" });\n\n config = config with { ActiveProvider = request.Provider };\n await SaveConfigAsync(config);\n\n return Results.Ok(new { message = $\"已切换到 {config.Providers[request.Provider].Name}\" });\n })\n .WithName(\"SwitchProvider\")\n .WithDescription(\"切换当前 API 提供商\");\n\n // PUT /api/config/llm/model — 切换当前模型\n group.MapPut(\"/model\", async (SwitchModelRequest request) =>\n {\n var config = await LoadConfigAsync();\n \n // 验证模型是否在当前提供商的可用模型列表中\n if (config.Providers.TryGetValue(config.ActiveProvider, out var provider) &&\n !provider.Models.Contains(request.Model))\n {\n return Results.BadRequest(new { error = $\"模型 '{request.Model}' 不在当前提供商的可用模型列表中\" });\n }\n\n config = config with { ActiveModel = request.Model };\n await SaveConfigAsync(config);\n\n return Results.Ok(new { message = $\"已切换到 {request.Model}\" });\n })\n .WithName(\"SwitchModel\")\n .WithDescription(\"切换当前模型\");\n\n // PUT /api/config/llm/provider/{id}/apikey — 更新指定 API 的 Key\n group.MapPut(\"/provider/{id}/apikey\", async (string id, UpdateApiKeyRequest request) =>\n {\n var config = await LoadConfigAsync();\n \n if (!config.Providers.TryGetValue(id, out var provider))\n return Results.BadRequest(new { error = $\"提供商 '{id}' 不存在\" });\n\n var updatedProvider = provider with { ApiKey = request.ApiKey };\n var providers = new Dictionary<string, LlmProvider>(config.Providers)\n {\n [id] = updatedProvider\n };\n config = config with { Providers = providers };\n await SaveConfigAsync(config);\n\n return Results.Ok(new { message = \"API Key 已更新\" });\n })\n .WithName(\"UpdateApiKey\")\n .WithDescription(\"更新指定 API 的 Key\");\n\n // PUT /api/config/llm/model/{id}/params — 更新模型参数\n group.MapPut(\"/model/{id}/params\", async (string id, UpdateModelParamsRequest request) =>\n {\n var config = await LoadConfigAsync();\n \n if (!config.ModelConfigs.ContainsKey(id))\n {\n // 如果模型配置不存在,创建默认配置\n config.ModelConfigs[id] = new ModelConfig();\n }\n\n var currentConfig = config.ModelConfigs[id];\n var updatedConfig = currentConfig with\n {\n Temperature = request.Temperature ?? currentConfig.Temperature,\n MaxTokens = request.MaxTokens ?? currentConfig.MaxTokens,\n TopP = request.TopP ?? currentConfig.TopP,\n Thinking = request.Thinking ?? currentConfig.Thinking\n };\n\n var modelConfigs = new Dictionary<string, ModelConfig>(config.ModelConfigs)\n {\n [id] = updatedConfig\n };\n config = config with { ModelConfigs = modelConfigs };\n await SaveConfigAsync(config);\n\n return Results.Ok(new { message = \"模型参数已更新\" });\n })\n .WithName(\"UpdateModelParams\")\n .WithDescription(\"更新模型参数\");\n\n // POST /api/config/llm/provider/{id}/test — 测试指定 API 连接\n group.MapPost(\"/provider/{id}/test\", async (string id, IHttpClientFactory httpClientFactory) =>\n {\n var config = await LoadConfigAsync();\n \n if (!config.Providers.TryGetValue(id, out var provider))\n return Results.BadRequest(new { error = $\"提供商 '{id}' 不存在\" });\n\n // llm-config.json 中 Key 为空时,从 config.json 兜底读取\n var apiKey = provider.ApiKey;\n if (string.IsNullOrWhiteSpace(apiKey))\n apiKey = GetHostConfigApiKey();\n if (string.IsNullOrWhiteSpace(apiKey))\n return Results.BadRequest(new { error = \"API Key 未配置\" });\n\n try\n {\n var startTime = DateTime.UtcNow;\n var httpClient = httpClientFactory.CreateClient();\n httpClient.Timeout = TimeSpan.FromSeconds(10);\n \n // 发送简单的模型列表请求测试连接\n var request = new HttpRequestMessage(HttpMethod.Get, $\"{provider.BaseUrl}/models\");\n request.Headers.Add(\"Authorization\", $\"Bearer {apiKey}\");\n \n var response = await httpClient.SendAsync(request);\n var latency = (int)(DateTime.UtcNow - startTime).TotalMilliseconds;\n\n if (response.IsSuccessStatusCode)\n {\n return Results.Ok(new\n {\n status = \"ok\",\n latency,\n model = config.ActiveModel,\n provider = provider.Name\n });\n }\n else\n {\n var errorContent = await response.Content.ReadAsStringAsync();\n return Results.Ok(new\n {\n status = \"error\",\n latency,\n error = $\"API 返回错误: {response.StatusCode}\",\n details = errorContent\n });\n }\n }\n catch (TaskCanceledException)\n {\n return Results.Ok(new { status = \"error\", error = \"连接超时\" });\n }\n catch (Exception ex)\n {\n return Results.Ok(new { status = \"error\", error = ex.Message });\n }\n })\n .WithName(\"TestProviderConnection\")\n .WithDescription(\"测试指定 API 连接\");\n }\n\n private static async Task<LlmFullConfig> LoadConfigAsync()\n {\n try\n {\n if (!File.Exists(ConfigFilePath))\n {\n return CreateDefaultConfig();\n }\n\n var json = await File.ReadAllTextAsync(ConfigFilePath);\n return JsonSerializer.Deserialize<LlmFullConfig>(json, JsonOptions) ?? CreateDefaultConfig();\n }\n catch\n {\n return CreateDefaultConfig();\n }\n }\n\n private static async Task SaveConfigAsync(LlmFullConfig config)\n {\n Directory.CreateDirectory(Path.GetDirectoryName(ConfigFilePath)!);\n var json = JsonSerializer.Serialize(config, JsonOptions);\n await File.WriteAllTextAsync(ConfigFilePath, json);\n }\n\n private static LlmFullConfig CreateDefaultConfig()\n {\n return new LlmFullConfig\n {\n ActiveProvider = \"mimo\",\n ActiveModel = \"mimo-v2.5-pro\",\n Providers = new Dictionary<string, LlmProvider>\n {\n [\"mimo\"] = new()\n {\n Name = \"MiMo API\",\n BaseUrl = \"https://api.xiaomimimo.com/v1\",\n Models = new List<string> { \"mimo-v2.5\", \"mimo-v2.5-pro\" }\n },\n [\"mimo-token-plan\"] = new()\n {\n Name = \"MiMo Token Plan\",\n BaseUrl = \"https://token-plan-cn.xiaomimimo.com/v1\",\n Models = new List<string> { \"mimo-v2.5\", \"mimo-v2.5-pro\" }\n },\n [\"deepseek\"] = new()\n {\n Name = \"DeepSeek API\",\n BaseUrl = \"https://api.deepseek.com/v1\",\n Models = new List<string> { \"deepseek-chat\", \"deepseek-coder\", \"deepseek-reasoner\" }\n }\n },\n ModelConfigs = new Dictionary<string, ModelConfig>\n {\n [\"mimo-v2.5\"] = new()\n {\n Temperature = 1.0,\n MaxTokens = 32768,\n TopP = 0.95,\n Thinking = true\n },\n [\"mimo-v2.5-pro\"] = new()\n {\n Temperature = 1.0,\n MaxTokens = 131072,\n TopP = 0.95,\n Thinking = true\n }\n }\n };\n }\n\n private static object MaskConfig(LlmFullConfig config)\n {\n return new\n {\n config.ActiveProvider,\n config.ActiveModel,\n Providers = config.Providers.ToDictionary(\n kvp => kvp.Key,\n kvp => new\n {\n kvp.Value.Name,\n kvp.Value.BaseUrl,\n ApiKey = MaskApiKey(kvp.Value.ApiKey),\n kvp.Value.Models\n }),\n config.ModelConfigs\n };\n }\n\n private static string MaskApiKey(string? key)\n {\n if (string.IsNullOrWhiteSpace(key)) return \"(未设置)\";\n if (key.Length <= 8) return \"***\";\n return $\"{key[..4]}...{key[^4..]}\";\n }\n\n /// <summary>从 config.json 读取 Host 实际使用的 API Key(兜底)</summary>\n private static string? GetHostConfigApiKey()\n {\n try\n {\n var configPath = Path.GetFullPath(\n Path.Combine(AppContext.BaseDirectory, \"..\", \"..\", \"..\", \"..\", \"..\", \"data\", \"config.json\"));\n if (!File.Exists(configPath)) return null;\n var json = File.ReadAllText(configPath);\n using var doc = JsonDocument.Parse(json);\n return doc.RootElement\n .GetProperty(\"Agent\")\n .GetProperty(\"Llm\")\n .GetProperty(\"ApiKey\")\n .GetString();\n }\n catch\n {\n return null;\n }\n }\n}\n\npublic sealed record SwitchProviderRequest(string Provider);\npublic sealed record SwitchModelRequest(string Model);\npublic sealed record UpdateApiKeyRequest(string ApiKey);\npublic sealed record UpdateModelParamsRequest(\n double? Temperature,\n int? MaxTokens,\n double? TopP,\n bool? Thinking);"}, "fa8f4fbe06825de36267614d052d24a9fd932d0b": {"t": 1785760663.7434206, "n": 11418, "h": "fc7946947e486cf9", "c": "// ============================================================================\n// LLM 配置管理 API — 多提供商支持\n// ============================================================================\n\nusing Agent.Core.Models;\nusing System.Text.Json;\n\nnamespace Agent.Host.Endpoints;\n\npublic static class LlmConfigEndpoints\n{\n private static readonly string ConfigFilePath = Path.GetFullPath(\n Path.Combine(AppContext.BaseDirectory, \"..\", \"..\", \"..\", \"..\", \"..\", \"data\", \"llm-config.json\"));\n\n private static readonly JsonSerializerOptions JsonOptions = new()\n {\n WriteIndented = true,\n PropertyNamingPolicy = JsonNamingPolicy.CamelCase\n };\n\n public static void MapLlmConfigEndpoints(this WebApplication app)\n {\n var group = app.MapGroup(\"/api/config/llm\")\n .WithTags(\"LLM Config\")\n .WithDescription(\"LLM 配置管理\");\n\n // GET /api/config/llm — 获取 LLM 配置(API Key 脱敏)\n group.MapGet(\"/\", async () =>\n {\n var config = await LoadConfigAsync();\n var maskedConfig = MaskConfig(config);\n return Results.Ok(maskedConfig);\n })\n .WithName(\"GetLlmConfig\")\n .WithDescription(\"获取 LLM 配置(API Key 脱敏)\");\n\n // PUT /api/config/llm/provider — 切换当前 API 提供商\n group.MapPut(\"/provider\", async (SwitchProviderRequest request) =>\n {\n var config = await LoadConfigAsync();\n \n if (!config.Providers.ContainsKey(request.Provider))\n return Results.BadRequest(new { error = $\"提供商 '{request.Provider}' 不存在\" });\n\n config = config with { ActiveProvider = request.Provider };\n await SaveConfigAsync(config);\n\n return Results.Ok(new { message = $\"已切换到 {config.Providers[request.Provider].Name}\" });\n })\n .WithName(\"SwitchProvider\")\n .WithDescription(\"切换当前 API 提供商\");\n\n // PUT /api/config/llm/model — 切换当前模型\n group.MapPut(\"/model\", async (SwitchModelRequest request) =>\n {\n var config = await LoadConfigAsync();\n \n // 验证模型是否在当前提供商的可用模型列表中\n if (config.Providers.TryGetValue(config.ActiveProvider, out var provider) &&\n !provider.Models.Contains(request.Model))\n {\n return Results.BadRequest(new { error = $\"模型 '{request.Model}' 不在当前提供商的可用模型列表中\" });\n }\n\n config = config with { ActiveModel = request.Model };\n await SaveConfigAsync(config);\n\n return Results.Ok(new { message = $\"已切换到 {request.Model}\" });\n })\n .WithName(\"SwitchModel\")\n .WithDescription(\"切换当前模型\");\n\n // PUT /api/config/llm/provider/{id}/apikey — 更新指定 API 的 Key\n group.MapPut(\"/provider/{id}/apikey\", async (string id, UpdateApiKeyRequest request) =>\n {\n var config = await LoadConfigAsync();\n \n if (!config.Providers.TryGetValue(id, out var provider))\n return Results.BadRequest(new { error = $\"提供商 '{id}' 不存在\" });\n\n var updatedProvider = provider with { ApiKey = request.ApiKey };\n var providers = new Dictionary<string, LlmProvider>(config.Providers)\n {\n [id] = updatedProvider\n };\n config = config with { Providers = providers };\n await SaveConfigAsync(config);\n\n return Results.Ok(new { message = \"API Key 已更新\" });\n })\n .WithName(\"UpdateApiKey\")\n .WithDescription(\"更新指定 API 的 Key\");\n\n // PUT /api/config/llm/model/{id}/params — 更新模型参数\n group.MapPut(\"/model/{id}/params\", async (string id, UpdateModelParamsRequest request) =>\n {\n var config = await LoadConfigAsync();\n \n if (!config.ModelConfigs.ContainsKey(id))\n {\n // 如果模型配置不存在,创建默认配置\n config.ModelConfigs[id] = new ModelConfig();\n }\n\n var currentConfig = config.ModelConfigs[id];\n var updatedConfig = currentConfig with\n {\n Temperature = request.Temperature ?? currentConfig.Temperature,\n MaxTokens = request.MaxTokens ?? currentConfig.MaxTokens,\n TopP = request.TopP ?? currentConfig.TopP,\n Thinking = request.Thinking ?? currentConfig.Thinking\n };\n\n var modelConfigs = new Dictionary<string, ModelConfig>(config.ModelConfigs)\n {\n [id] = updatedConfig\n };\n config = config with { ModelConfigs = modelConfigs };\n await SaveConfigAsync(config);\n\n return Results.Ok(new { message = \"模型参数已更新\" });\n })\n .WithName(\"UpdateModelParams\")\n .WithDescription(\"更新模型参数\");\n\n // POST /api/config/llm/provider/{id}/test — 测试指定 API 连接\n group.MapPost(\"/provider/{id}/test\", async (string id, IHttpClientFactory httpClientFactory) =>\n {\n var config = await LoadConfigAsync();\n \n if (!config.Providers.TryGetValue(id, out var provider))\n return Results.BadRequest(new { error = $\"提供商 '{id}' 不存在\" });\n\n // llm-config.json 中 Key 为空时,从 config.json 兜底读取\n var apiKey = provider.ApiKey;\n if (string.IsNullOrWhiteSpace(apiKey))\n apiKey = GetHostConfigApiKey();\n if (string.IsNullOrWhiteSpace(apiKey))\n return Results.BadRequest(new { error = \"API Key 未配置\" });\n\n try\n {\n var startTime = DateTime.UtcNow;\n var httpClient = httpClientFactory.CreateClient();\n httpClient.Timeout = TimeSpan.FromSeconds(10);\n \n // 发送简单的模型列表请求测试连接\n var request = new HttpRequestMessage(HttpMethod.Get, $\"{provider.BaseUrl}/models\");\n request.Headers.Add(\"Authorization\", $\"Bearer {apiKey}\");\n \n var response = await httpClient.SendAsync(request);\n var latency = (int)(DateTime.UtcNow - startTime).TotalMilliseconds;\n\n if (response.IsSuccessStatusCode)\n {\n return Results.Ok(new\n {\n status = \"ok\",\n latency,\n model = config.ActiveModel,\n provider = provider.Name\n });\n }\n else\n {\n var errorContent = await response.Content.ReadAsStringAsync();\n return Results.Ok(new\n {\n status = \"error\",\n latency,\n error = $\"API 返回错误: {response.StatusCode}\",\n details = errorContent\n });\n }\n }\n catch (TaskCanceledException)\n {\n return Results.Ok(new { status = \"error\", error = \"连接超时\" });\n }\n catch (Exception ex)\n {\n return Results.Ok(new { status = \"error\", error = ex.Message });\n }\n })\n .WithName(\"TestProviderConnection\")\n .WithDescription(\"测试指定 API 连接\");\n }\n\n private static async Task<LlmFullConfig> LoadConfigAsync()\n {\n try\n {\n if (!File.Exists(ConfigFilePath))\n {\n return CreateDefaultConfig();\n }\n\n var json = await File.ReadAllTextAsync(ConfigFilePath);\n return JsonSerializer.Deserialize<LlmFullConfig>(json, JsonOptions) ?? CreateDefaultConfig();\n }\n catch\n {\n return CreateDefaultConfig();\n }\n }\n\n private static async Task SaveConfigAsync(LlmFullConfig config)\n {\n Directory.CreateDirectory(Path.GetDirectoryName(ConfigFilePath)!);\n var json = JsonSerializer.Serialize(config, JsonOptions);\n await File.WriteAllTextAsync(ConfigFilePath, json);\n }\n\n private static LlmFullConfig CreateDefaultConfig()\n {\n return new LlmFullConfig\n {\n ActiveProvider = \"mimo\",\n ActiveModel = \"mimo-v2.5-pro\",\n Providers = new Dictionary<string, LlmProvider>\n {\n [\"mimo\"] = new()\n {\n Name = \"MiMo API\",\n BaseUrl = \"https://api.xiaomimimo.com/v1\",\n Models = new List<string> { \"mimo-v2.5\", \"mimo-v2.5-pro\" }\n },\n [\"mimo-token-plan\"] = new()\n {\n Name = \"MiMo Token Plan\",\n BaseUrl = \"https://token-plan-cn.xiaomimimo.com/v1\",\n Models = new List<string> { \"mimo-v2.5\", \"mimo-v2.5-pro\" }\n },\n [\"deepseek\"] = new()\n {\n Name = \"DeepSeek API\",\n BaseUrl = \"https://api.deepseek.com/v1\",\n Models = new List<string> { \"deepseek-chat\", \"deepseek-coder\", \"deepseek-reasoner\" }\n }\n },\n ModelConfigs = new Dictionary<string, ModelConfig>\n {\n [\"mimo-v2.5\"] = new()\n {\n Temperature = 1.0,\n MaxTokens = 32768,\n TopP = 0.95,\n Thinking = true\n },\n [\"mimo-v2.5-pro\"] = new()\n {\n Temperature = 1.0,\n MaxTokens = 131072,\n TopP = 0.95,\n Thinking = true\n }\n }\n };\n }\n\n private static object MaskConfig(LlmFullConfig config)\n {\n return new\n {\n config.ActiveProvider,\n config.ActiveModel,\n Providers = config.Providers.ToDictionary(\n kvp => kvp.Key,\n kvp => new\n {\n kvp.Value.Name,\n kvp.Value.BaseUrl,\n ApiKey = MaskApiKey(kvp.Value.ApiKey),\n kvp.Value.Models\n }),\n config.ModelConfigs\n };\n }\n\n private static string MaskApiKey(string? key)\n {\n if (string.IsNullOrWhiteSpace(key)) return \"(未设置)\";\n if (key.Length <= 8) return \"***\";\n return $\"{key[..4]}...{key[^4..]}\";\n }\n\n /// <summary>从 config.json 读取 Host 实际使用的 API Key(兜底)</summary>\n private static string? GetHostConfigApiKey()\n {\n try\n {\n var configPath = Path.GetFullPath(\n Path.Combine(AppContext.BaseDirectory, \"..\", \"..\", \"..\", \"..\", \"..\", \"data\", \"config.json\"));\n if (!File.Exists(configPath)) return null;\n var json = File.ReadAllText(configPath);\n using var doc = JsonDocument.Parse(json);\n return doc.RootElement\n .GetProperty(\"Agent\")\n .GetProperty(\"Llm\")\n .GetProperty(\"ApiKey\")\n .GetString();\n }\n catch\n {\n return null;\n }\n }\n}\n\npublic sealed record SwitchProviderRequest(string Provider);\npublic sealed record SwitchModelRequest(string Model);\npublic sealed record UpdateApiKeyRequest(string ApiKey);\npublic sealed record UpdateModelParamsRequest(\n double? Temperature,\n int? MaxTokens,\n double? TopP,\n bool? Thinking);"}}}}