1
0
Files
Toak/Api/OpenAiCompatibleClient.cs

145 lines
6.2 KiB
C#

using System.Net.Http.Headers;
using System.Text.Json;
using Toak.Api.Models;
using Toak.Serialization;
using Toak.Core;
using Toak.Core.Interfaces;
namespace Toak.Api;
public class OpenAiCompatibleClient : ISpeechClient, ILlmClient
{
private readonly HttpClient _httpClient;
private readonly string? _reasoningEffort;
public OpenAiCompatibleClient(string apiKey, string baseUrl = "https://api.groq.com/openai/v1/", string? reasoningEffort = null)
{
_httpClient = new HttpClient();
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", apiKey);
_httpClient.BaseAddress = new Uri(baseUrl);
_reasoningEffort = reasoningEffort == "none" ? null : reasoningEffort;
}
public async Task<string> TranscribeAsync(string filePath, string language = "", string model = Constants.Defaults.WhisperModel)
{
// ... (TranscribeAsync content remains same except maybe some internal comments or contexts)
using var content = new MultipartFormDataContent();
await using var fileStream = File.OpenRead(filePath);
using var streamContent = new StreamContent(fileStream);
streamContent.Headers.ContentType = new MediaTypeHeaderValue("audio/wav"); // or mpeg
content.Add(streamContent, "file", Path.GetFileName(filePath));
var modelToUse = string.IsNullOrWhiteSpace(model) ? Constants.Defaults.WhisperModel : model;
content.Add(new StringContent(modelToUse), "model");
if (!string.IsNullOrWhiteSpace(language))
{
var firstLang = language.Split(',')[0].Trim();
content.Add(new StringContent(firstLang), "language");
}
Logger.LogDebug($"Sending Whisper API request ({modelToUse})...");
var response = await _httpClient.PostAsync("audio/transcriptions", content);
Logger.LogDebug($"Whisper API response status: {response.StatusCode}");
if (!response.IsSuccessStatusCode)
{
var error = await response.Content.ReadAsStringAsync();
throw new Exception($"Whisper API Error: {response.StatusCode} - {error}");
}
var json = await response.Content.ReadAsStringAsync();
var result = JsonSerializer.Deserialize(json, AppJsonSerializerContext.Default.WhisperResponse);
return result?.Text ?? string.Empty;
}
public async Task<string> RefineTextAsync(string rawTranscript, string systemPrompt, string model = Constants.Defaults.LlmModel)
{
var requestBody = new OpenAiRequest
{
Model = string.IsNullOrWhiteSpace(model) ? Constants.Defaults.LlmModel : model,
Temperature = 0.0,
ReasoningEffort = _reasoningEffort,
Messages =
[
new OpenAiRequestMessage { Role = "system", Content = systemPrompt },
new OpenAiRequestMessage { Role = "user", Content = $"<transcript>{rawTranscript}</transcript>" }
]
};
var jsonContent = new StringContent(JsonSerializer.Serialize(requestBody, AppJsonSerializerContext.Default.OpenAiRequest), System.Text.Encoding.UTF8, "application/json");
Logger.LogDebug($"Sending OpenAi API request (model: {requestBody.Model})...");
var response = await _httpClient.PostAsync("chat/completions", jsonContent);
Logger.LogDebug($"OpenAi API response status: {response.StatusCode}");
if (!response.IsSuccessStatusCode)
{
var error = await response.Content.ReadAsStringAsync();
throw new Exception($"OpenAi API Error: {response.StatusCode} - {error}");
}
var json = await response.Content.ReadAsStringAsync();
var result = JsonSerializer.Deserialize(json, AppJsonSerializerContext.Default.OpenAiResponse);
return result?.Choices?.FirstOrDefault()?.Message?.Content ?? string.Empty;
}
public async IAsyncEnumerable<string> RefineTextStreamAsync(string rawTranscript, string systemPrompt, string model = Constants.Defaults.LlmModel)
{
var requestBody = new OpenAiRequest
{
Model = string.IsNullOrWhiteSpace(model) ? Constants.Defaults.LlmModel : model,
Temperature = 0.0,
Stream = true,
ReasoningEffort = _reasoningEffort,
Messages =
[
new OpenAiRequestMessage { Role = "system", Content = systemPrompt },
new OpenAiRequestMessage { Role = "user", Content = $"<transcript>{rawTranscript}</transcript>" }
]
};
var jsonContent = new StringContent(JsonSerializer.Serialize(requestBody, AppJsonSerializerContext.Default.OpenAiRequest), System.Text.Encoding.UTF8, "application/json");
using var request = new HttpRequestMessage(HttpMethod.Post, "chat/completions");
request.Content = jsonContent;
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("text/event-stream"));
Logger.LogDebug($"Sending OpenAi Steam API request (model: {requestBody.Model})...");
using var response = await _httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
Logger.LogDebug($"OpenAi Stream API response status: {response.StatusCode}");
if (!response.IsSuccessStatusCode)
{
var error = await response.Content.ReadAsStringAsync();
throw new Exception($"OpenAi API Error: {response.StatusCode} - {error}");
}
await using var stream = await response.Content.ReadAsStreamAsync();
using var reader = new StreamReader(stream);
string? line;
while ((line = await reader.ReadLineAsync()) != null)
{
if (string.IsNullOrWhiteSpace(line)) continue;
if (line.StartsWith("data: "))
{
var data = line.Substring("data: ".Length).Trim();
if (data == "[DONE]") break;
var chunk = JsonSerializer.Deserialize(data, AppJsonSerializerContext.Default.OpenAiStreamResponse);
var content = chunk?.Choices?.FirstOrDefault()?.Delta?.Content;
if (!string.IsNullOrEmpty(content))
{
yield return content;
}
}
}
}
}