All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
- 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>
232 lines
6.7 KiB
C#
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>();
|
|
}
|