DeutschLernen/GermanApp/Application/Models/MistralResponse.cs
Lasse Rune Hansen 2b11367a97
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
feat(backend/infrastructure): implement Mistral API Connector (Phase 0 of AI Services)
- Add Domain/Interfaces/IMistralConnector.cs with AiServiceException and AiErrorCode enum
- Add Infrastructure/Configuration/MistralConfig.cs with validation
- Add Infrastructure/Services/MistralConnector.cs with HTTP client implementation
- Add Infrastructure/Services/MistralRateLimiter.cs for rate limiting
- Add Infrastructure/Services/MistralCircuitBreaker.cs for circuit breaker pattern
- Add Application/Models/MistralRequest.cs with request models
- Add Application/Models/MistralResponse.cs with response models
- Update Program.cs to register IMistralConnector with MemoryCache, HttpClient, and config
- Update GermanApp.csproj with existing dependencies
- Update docs/features/ai-services.md with Phase 0 completion

Phase 0 of AI Services feature is complete. Mistral API Connector is ready for use by MistralService.

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
2026-06-09 18:56:37 +02:00

232 lines
6.7 KiB
C#

using System.Text.Json.Serialization;
using GermanApp.Domain.Interfaces;
namespace GermanApp.Application.Models;
/// <summary>
/// Response model for Mistral completion and chat completion APIs.
/// </summary>
/// <remarks>
/// See: https://docs.mistral.ai/api/#operation/createCompletion
/// See: https://docs.mistral.ai/api/#operation/createChatCompletion
/// </remarks>
public record MistralResponse
{
/// <summary>
/// Unique identifier for the completion.
/// </summary>
[JsonPropertyName("id")]
public string Id { get; init; } = string.Empty;
/// <summary>
/// The object type (always "text_completion" or "chat.completion").
/// </summary>
[JsonPropertyName("object")]
public string Object { get; init; } = string.Empty;
/// <summary>
/// The Unix timestamp (in seconds) of when the completion was created.
/// </summary>
[JsonPropertyName("created")]
public long Created { get; init; }
/// <summary>
/// The model used for the completion.
/// </summary>
[JsonPropertyName("model")]
public string Model { get; init; } = string.Empty;
/// <summary>
/// The list of completion choices generated by the model.
/// </summary>
[JsonPropertyName("choices")]
public IReadOnlyList<MistralChoice> Choices { get; init; } = new List<MistralChoice>();
/// <summary>
/// Usage statistics for the completion.
/// </summary>
[JsonPropertyName("usage")]
public MistralUsage Usage { get; init; } = new();
/// <summary>
/// Gets the first choice's text content.
/// Convenience property for single-completion requests.
/// </summary>
[JsonIgnore]
public string FirstChoiceText => Choices.Count > 0 ? Choices[0].Text : string.Empty;
/// <summary>
/// Gets the first choice's message content (for chat completions).
/// </summary>
[JsonIgnore]
public string FirstChoiceMessageContent =>
Choices.Count > 0 && Choices[0].Message != null
? Choices[0].Message.Content
: string.Empty;
/// <summary>
/// Gets all generated texts from all choices.
/// </summary>
[JsonIgnore]
public IReadOnlyList<string> AllChoiceTexts => Choices.Select(c => c.Text).ToList();
/// <summary>
/// Checks if the response contains any valid completions.
/// </summary>
[JsonIgnore]
public bool HasValidChoices => Choices != null && Choices.Count > 0 &&
Choices.Any(c => !string.IsNullOrWhiteSpace(c.Text) || c.Message != null);
}
/// <summary>
/// A single completion choice returned by Mistral API.
/// </summary>
public record MistralChoice
{
/// <summary>
/// The generated text for this choice.
/// </summary>
[JsonPropertyName("text")]
public string Text { get; init; } = string.Empty;
/// <summary>
/// For chat completions, this contains the message object.
/// </summary>
[JsonPropertyName("message")]
public MistralChatMessage? Message { get; init; }
/// <summary>
/// The reason the model stopped generating tokens (e.g., "stop", "length", "error").
/// </summary>
[JsonPropertyName("finish_reason")]
public string FinishReason { get; init; } = string.Empty;
/// <summary>
/// The index of this choice in the list of choices.
/// </summary>
[JsonPropertyName("index")]
public int Index { get; init; }
/// <summary>
/// Log probability information for the generated tokens.
/// </summary>
[JsonPropertyName("logprobs")]
public object? LogProbs { get; init; }
}
/// <summary>
/// Message object returned in chat completion choices.
/// </summary>
public record MistralChatMessage
{
/// <summary>
/// The role of the message author.
/// </summary>
[JsonPropertyName("role")]
public string Role { get; init; } = string.Empty;
/// <summary>
/// The content of the message.
/// </summary>
[JsonPropertyName("content")]
public string Content { get; init; } = string.Empty;
}
/// <summary>
/// Token usage statistics from Mistral API.
/// </summary>
public record MistralUsage
{
/// <summary>
/// Number of tokens in the prompt.
/// </summary>
[JsonPropertyName("prompt_tokens")]
public int PromptTokens { get; init; }
/// <summary>
/// Number of tokens generated in the completion.
/// </summary>
[JsonPropertyName("completion_tokens")]
public int CompletionTokens { get; init; }
/// <summary>
/// Total number of tokens used (prompt + completion).
/// </summary>
[JsonPropertyName("total_tokens")]
public int TotalTokens { get; init; }
/// <summary>
/// Gets the total cost estimate based on token usage.
/// Note: Actual pricing may vary based on Mistral's pricing model.
/// </summary>
[JsonIgnore]
public decimal EstimatedCost => TotalTokens * 0.0000025m; // Approx $0.0000025 per token for mistral-medium
}
/// <summary>
/// Error response from Mistral API.
/// </summary>
public record MistralErrorResponse
{
/// <summary>
/// The object type (always "error").
/// </summary>
[JsonPropertyName("object")]
public string Object { get; init; } = string.Empty;
/// <summary>
/// The error message.
/// </summary>
[JsonPropertyName("message")]
public string Message { get; init; } = string.Empty;
/// <summary>
/// The type of error that occurred.
/// </summary>
[JsonPropertyName("type")]
public string Type { get; init; } = string.Empty;
/// <summary>
/// The HTTP status code associated with the error.
/// </summary>
[JsonIgnore]
public int? StatusCode { get; init; }
/// <summary>
/// Additional error details.
/// </summary>
[JsonPropertyName("details")]
public object? Details { get; init; }
/// <summary>
/// Maps the error type to an AiErrorCode.
/// </summary>
[JsonIgnore]
public AiErrorCode ErrorCode => Type switch
{
"rate_limit_exceeded" or "rate_limit" => AiErrorCode.RateLimited,
"invalid_request_error" => AiErrorCode.InvalidRequest,
"authentication_error" or "invalid_api_key" => AiErrorCode.AuthenticationError,
"server_error" or "internal_server_error" => AiErrorCode.Temporary,
"timeout" => AiErrorCode.Timeout,
_ => AiErrorCode.Unknown
};
}
/// <summary>
/// Response wrapper for listing Mistral models.
/// </summary>
public record MistralListModelsResponse
{
/// <summary>
/// The object type (always "list").
/// </summary>
[JsonPropertyName("object")]
public string Object { get; init; } = string.Empty;
/// <summary>
/// List of available models.
/// </summary>
[JsonPropertyName("data")]
public IReadOnlyList<MistralModel> Data { get; init; } = new List<MistralModel>();
}