aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRoy Ben Shabat <roy.mail.net@gmail.com>2025-09-04 13:42:02 +0300
committerRoy Ben Shabat <roy.mail.net@gmail.com>2025-09-04 13:42:02 +0300
commit72d2d9316e27f623456574dd854da064611254a1 (patch)
tree85293806e5e432d8ab6b96e03e084ab5a1599c08
parent13f9257daed202db98442f4a97167fd4d0e09e14 (diff)
downloadTango-72d2d9316e27f623456574dd854da064611254a1.tar.gz
Tango-72d2d9316e27f623456574dd854da064611254a1.zip
OpenAI & Claude Providers.
-rw-r--r--Software/Visual_Studio_22/.claude/settings.local.json6
-rw-r--r--Software/Visual_Studio_22/Tango.Portal.Chat.Web/Models/Options.cs22
-rw-r--r--Software/Visual_Studio_22/Tango.Portal.Chat.Web/Services/LlmClient.cs73
-rw-r--r--Software/Visual_Studio_22/Tango.Portal.Chat.Web/appsettings.Development.json8
-rw-r--r--Software/Visual_Studio_22/Tango.Portal.Chat.Web/appsettings.json8
5 files changed, 103 insertions, 14 deletions
diff --git a/Software/Visual_Studio_22/.claude/settings.local.json b/Software/Visual_Studio_22/.claude/settings.local.json
index 347ddd8ef..eb6c45fa9 100644
--- a/Software/Visual_Studio_22/.claude/settings.local.json
+++ b/Software/Visual_Studio_22/.claude/settings.local.json
@@ -7,7 +7,11 @@
"Read(/C:\\DATA\\Development\\Tango\\Software\\Visual_Studio_22/**)",
"Read(/C:\\DATA\\Development\\Tango\\Software\\Visual_Studio_22\\Tango.Portal.Chat.Web\\Controllers/**)",
"Read(/C:\\DATA\\Development\\Tango\\Software\\Visual_Studio_22\\Tango.Portal.Chat.Web\\Services/**)",
- "Bash(dotnet --version)"
+ "Bash(dotnet --version)",
+ "Read(/C:\\DATA\\Development\\Tango\\Software\\Visual_Studio_22\\Tango.Portal.Chat.Web\\Services/**)",
+ "Read(/C:\\DATA\\Development\\Tango\\Software\\Visual_Studio_22\\Tango.Portal.Chat.Web\\Models/**)",
+ "Read(/C:\\DATA\\Development\\Tango\\Software\\Visual_Studio_22\\Tango.Portal.Chat.Web/**)",
+ "Read(/C:\\DATA\\Development\\Tango\\Software\\Visual_Studio_22\\Tango.Portal.Chat.Web\\Models/**)"
],
"deny": [],
"ask": []
diff --git a/Software/Visual_Studio_22/Tango.Portal.Chat.Web/Models/Options.cs b/Software/Visual_Studio_22/Tango.Portal.Chat.Web/Models/Options.cs
index bfd8fdaf5..6c6a537d6 100644
--- a/Software/Visual_Studio_22/Tango.Portal.Chat.Web/Models/Options.cs
+++ b/Software/Visual_Studio_22/Tango.Portal.Chat.Web/Models/Options.cs
@@ -1,16 +1,30 @@
namespace Tango.Portal.Chat.Web.Services
{
+ public enum LlmProvider
+ {
+ OpenAI,
+ Claude
+ }
+
public sealed class LlmOptions
{
- // If using Azure OpenAI, set IsAzure = true and Endpoint = full chat completions URL (with api-version query).
- // If using OpenAI, set IsAzure = false and Endpoint = https://api.openai.com/v1/chat/completions
- public bool IsAzure { get; set; } = false;
+ // Provider selection: OpenAI, AzureOpenAI, or Claude
+ public LlmProvider Provider { get; set; } = LlmProvider.OpenAI;
+
+ // If using OpenAI, set Provider = OpenAI and Endpoint = https://api.openai.com/v1/chat/completions
+ // If using Claude, set Provider = Claude and Endpoint = https://api.anthropic.com/v1/messages
public string Endpoint { get; set; } = string.Empty;
public string ApiKey { get; set; } = string.Empty;
- public string Model { get; set; } = "gpt-4o-mini"; // or your Azure deployment name
+ public string Model { get; set; } = "gpt-4o-mini"; // or your Azure deployment name, or claude-3-5-sonnet-20241022
public double Temperature { get; set; } = 0.2;
public string AnswererAssistantId { get; set; } = string.Empty; // NEW
public string DocsAssistantId { get; set; } = string.Empty; // NEW
+
+ // Claude-specific settings
+ public string ClaudeApiKey { get; set; } = string.Empty;
+ public string ClaudeModel { get; set; } = "claude-3-5-sonnet-20241022";
+ public string ClaudeEndpoint { get; set; } = "https://api.anthropic.com/v1/messages";
+ public int MaxTokens { get; set; } = 4000;
}
public sealed class AdxOptions
diff --git a/Software/Visual_Studio_22/Tango.Portal.Chat.Web/Services/LlmClient.cs b/Software/Visual_Studio_22/Tango.Portal.Chat.Web/Services/LlmClient.cs
index fc970d52e..7b2d8c3ef 100644
--- a/Software/Visual_Studio_22/Tango.Portal.Chat.Web/Services/LlmClient.cs
+++ b/Software/Visual_Studio_22/Tango.Portal.Chat.Web/Services/LlmClient.cs
@@ -34,6 +34,17 @@ namespace Tango.Portal.Chat.Web.Services
public async Task<ProposeKqlResult> ProposeKqlAsync(String plannerPrompt, String plotySample,
string question, string schemaJson, IEnumerable<ChatMessage>? history, CancellationToken ct = default)
{
+ return _opt.Provider switch
+ {
+ LlmProvider.Claude => await ProposeKqlWithClaudeAsync(plannerPrompt, plotySample, question, schemaJson, history, ct),
+ LlmProvider.OpenAI => await ProposeKqlWithOpenAIAsync(plannerPrompt, plotySample, question, schemaJson, history, ct),
+ _ => await ProposeKqlWithOpenAIAsync(plannerPrompt, plotySample, question, schemaJson, history, ct) // Default to OpenAI
+ };
+ }
+
+ private async Task<ProposeKqlResult> ProposeKqlWithOpenAIAsync(String plannerPrompt, String plotySample,
+ string question, string schemaJson, IEnumerable<ChatMessage>? history, CancellationToken ct)
+ {
var messages = new List<object> { new { role = "system", content = plannerPrompt } };
if (history != null)
@@ -55,8 +66,8 @@ namespace Tango.Portal.Chat.Web.Services
using var req = new HttpRequestMessage(HttpMethod.Post, _opt.Endpoint);
req.Content = new StringContent(JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json");
- if (_opt.IsAzure) req.Headers.Add("api-key", _opt.ApiKey);
- else req.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _opt.ApiKey);
+
+ req.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _opt.ApiKey);
using var resp = await _http.SendAsync(req, ct);
resp.EnsureSuccessStatusCode();
@@ -64,7 +75,60 @@ namespace Tango.Portal.Chat.Web.Services
var root = JsonNode.Parse(body)!.AsObject();
var content = root["choices"]![0]!["message"]!["content"]?.ToString() ?? "{}";
- content = StripCodeFences(content); // your existing helper
+ content = StripCodeFences(content);
+
+ var opts = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
+ opts.Converters.Add(new FlexibleStringListConverter());
+ var result = JsonSerializer.Deserialize<ProposeKqlResult>(content, opts)
+ ?? new ProposeKqlResult { Kql = "", Parameters = new() };
+ return result;
+ }
+
+ private async Task<ProposeKqlResult> ProposeKqlWithClaudeAsync(String plannerPrompt, String plotySample,
+ string question, string schemaJson, IEnumerable<ChatMessage>? history, CancellationToken ct)
+ {
+ var messages = new List<object>();
+
+ if (history != null)
+ {
+ foreach (var m in history.TakeLast(6))
+ {
+ messages.Add(new {
+ role = m.Role == "assistant" ? "assistant" : "user",
+ content = CapString(m.Content, 1000)
+ });
+ }
+ }
+
+ var schemaBlock = $"SCHEMA:\n{schemaJson}";
+ var userMessage = $"Question: {question}\n\n{schemaBlock}\n\n{plotySample}\n\nPlease respond with valid JSON only.";
+ messages.Add(new { role = "user", content = userMessage });
+
+ var payload = new
+ {
+ model = !string.IsNullOrEmpty(_opt.ClaudeModel) ? _opt.ClaudeModel : "claude-3-5-sonnet-20241022",
+ max_tokens = _opt.MaxTokens,
+ temperature = _opt.Temperature,
+ system = plannerPrompt,
+ messages = messages
+ };
+
+ var endpoint = !string.IsNullOrEmpty(_opt.ClaudeEndpoint) ? _opt.ClaudeEndpoint : "https://api.anthropic.com/v1/messages";
+ var apiKey = !string.IsNullOrEmpty(_opt.ClaudeApiKey) ? _opt.ClaudeApiKey : _opt.ApiKey;
+
+ using var req = new HttpRequestMessage(HttpMethod.Post, endpoint);
+ req.Content = new StringContent(JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json");
+ req.Headers.Add("x-api-key", apiKey);
+ req.Headers.Add("anthropic-version", "2023-06-01");
+
+ using var resp = await _http.SendAsync(req, ct);
+ resp.EnsureSuccessStatusCode();
+ var body = await resp.Content.ReadAsStringAsync(ct);
+
+ var root = JsonNode.Parse(body)!.AsObject();
+ var contentArray = root["content"]?.AsArray();
+ var content = contentArray?[0]?["text"]?.ToString() ?? "{}";
+ content = StripCodeFences(content);
var opts = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
opts.Converters.Add(new FlexibleStringListConverter());
@@ -123,8 +187,7 @@ namespace Tango.Portal.Chat.Web.Services
var json = JsonSerializer.Serialize(payload);
req.Content = new StringContent(json, Encoding.UTF8, "application/json");
- if (_opt.IsAzure) req.Headers.Add("api-key", _opt.ApiKey);
- else req.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _opt.ApiKey);
+ req.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _opt.ApiKey);
using var resp = await _http.SendAsync(req, ct);
resp.EnsureSuccessStatusCode();
diff --git a/Software/Visual_Studio_22/Tango.Portal.Chat.Web/appsettings.Development.json b/Software/Visual_Studio_22/Tango.Portal.Chat.Web/appsettings.Development.json
index 8a35484d1..62f61e974 100644
--- a/Software/Visual_Studio_22/Tango.Portal.Chat.Web/appsettings.Development.json
+++ b/Software/Visual_Studio_22/Tango.Portal.Chat.Web/appsettings.Development.json
@@ -1,12 +1,16 @@
{
"OpenAI": {
- "IsAzure": false,
+ "Provider": "Claude",
"Endpoint": "https://api.openai.com/v1/chat/completions",
"ApiKey": "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",
"Model": "gpt-4o",
"Temperature": 0.2,
"AnswererAssistantId": "asst_JRKGFqWUYG2rP6CptUgyVcJk",
- "DocsAssistantId": "asst_HQ0C8tsdtzjENITM4qq6kFpz"
+ "DocsAssistantId": "asst_HQ0C8tsdtzjENITM4qq6kFpz",
+ "ClaudeApiKey": "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",
+ "ClaudeModel": "claude-sonnet-4-20250514",
+ "ClaudeEndpoint": "https://api.anthropic.com/v1/messages",
+ "MaxTokens": 10000
},
"ADX": {
"ClusterUri": "https://adx-twine.westeurope.kusto.windows.net/",
diff --git a/Software/Visual_Studio_22/Tango.Portal.Chat.Web/appsettings.json b/Software/Visual_Studio_22/Tango.Portal.Chat.Web/appsettings.json
index e126e16bb..4ea92d050 100644
--- a/Software/Visual_Studio_22/Tango.Portal.Chat.Web/appsettings.json
+++ b/Software/Visual_Studio_22/Tango.Portal.Chat.Web/appsettings.json
@@ -6,13 +6,17 @@
}
},
"OpenAI": {
- "IsAzure": false,
+ "Provider": "OpenAI",
"Endpoint": "https://api.openai.com/v1/chat/completions",
"ApiKey": "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",
"Model": "gpt-4o",
"Temperature": 0.2,
"AnswererAssistantId": "asst_JRKGFqWUYG2rP6CptUgyVcJk",
- "DocsAssistantId": "asst_HQ0C8tsdtzjENITM4qq6kFpz"
+ "DocsAssistantId": "asst_HQ0C8tsdtzjENITM4qq6kFpz",
+ "ClaudeApiKey": "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",
+ "ClaudeModel": "claude-3-5-sonnet-4",
+ "ClaudeEndpoint": "https://api.anthropic.com/v1/messages",
+ "MaxTokens": 4000
},
"ADX": {
"ClusterUri": "https://adx-twine.westeurope.kusto.windows.net/",