81 lines
2.8 KiB
C#
81 lines
2.8 KiB
C#
using System.Net.Http.Headers;
|
|
using System.Text;
|
|
using System.Text.Json;
|
|
using Microsoft.Extensions.Options;
|
|
|
|
namespace MealPlan.Api.Services.AI;
|
|
|
|
public class OpenAiChatService : IOpenAiChatService
|
|
{
|
|
private readonly HttpClient _http;
|
|
private readonly OpenAiOptions _options;
|
|
private readonly ILogger<OpenAiChatService> _logger;
|
|
|
|
public OpenAiChatService(HttpClient http, IOptions<OpenAiOptions> options, ILogger<OpenAiChatService> logger)
|
|
{
|
|
_http = http;
|
|
_options = options.Value;
|
|
_logger = logger;
|
|
}
|
|
|
|
public bool IsConfigured => _options.IsConfigured;
|
|
|
|
public async Task<string> CompleteJsonAsync(
|
|
string systemPrompt,
|
|
string userPrompt,
|
|
OpenAiUseCase useCase = OpenAiUseCase.Default,
|
|
CancellationToken ct = default)
|
|
{
|
|
if (!IsConfigured)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"OpenAI is not configured. Set OpenAI__ApiKey in appsettings.Local.json.");
|
|
}
|
|
|
|
var model = _options.ResolveModel(useCase);
|
|
var payload = new
|
|
{
|
|
model,
|
|
max_tokens = _options.MaxTokens,
|
|
response_format = new { type = "json_object" },
|
|
messages = new[]
|
|
{
|
|
new { role = "system", content = systemPrompt },
|
|
new { role = "user", content = userPrompt },
|
|
},
|
|
};
|
|
|
|
using var request = new HttpRequestMessage(HttpMethod.Post, "https://api.openai.com/v1/chat/completions");
|
|
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _options.ApiKey);
|
|
request.Content = new StringContent(JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json");
|
|
|
|
using var response = await _http.SendAsync(request, ct);
|
|
var body = await response.Content.ReadAsStringAsync(ct);
|
|
if (!response.IsSuccessStatusCode)
|
|
{
|
|
_logger.LogWarning("OpenAI API error {Status}: {Body}", response.StatusCode, body);
|
|
throw new InvalidOperationException("OpenAI request failed. Check API key and quota.");
|
|
}
|
|
|
|
using var doc = JsonDocument.Parse(body);
|
|
var content = doc.RootElement
|
|
.GetProperty("choices")[0]
|
|
.GetProperty("message")
|
|
.GetProperty("content")
|
|
.GetString() ?? "{}";
|
|
|
|
return StripMarkdownFence(content);
|
|
}
|
|
|
|
private static string StripMarkdownFence(string content)
|
|
{
|
|
var trimmed = content.Trim();
|
|
if (!trimmed.StartsWith("```", StringComparison.Ordinal)) return trimmed;
|
|
var firstNewline = trimmed.IndexOf('\n');
|
|
if (firstNewline < 0) return trimmed;
|
|
var endFence = trimmed.LastIndexOf("```", StringComparison.Ordinal);
|
|
if (endFence <= firstNewline) return trimmed;
|
|
return trimmed[(firstNewline + 1)..endFence].Trim();
|
|
}
|
|
}
|