├── ChatAIze.GenerativeCS ├── icon.png ├── Enums │ ├── Verbosity.cs │ ├── VoiceResponseFormat.cs │ ├── ReasoningEffort.cs │ ├── TextToSpeechVoice.cs │ └── TranscriptionResponseFormat.cs ├── Constants │ ├── EnvironmentVariables.cs │ ├── TextToSpeechModels.cs │ ├── SpeechRecognitionModels.cs │ ├── EmbeddingModels.cs │ ├── ModerationModels.cs │ ├── DefaultModels.cs │ └── ChatCompletionModels.cs ├── Utilities │ ├── EnvironmentVariableManager.cs │ ├── TokenUsageTracker.cs │ ├── RepeatingHttpClient.cs │ └── MessageTools.cs ├── Options │ ├── OpenAI │ │ ├── ModerationOptions.cs │ │ ├── EmbeddingOptions.cs │ │ ├── TranslationOptions.cs │ │ ├── TextToSpeechOptions.cs │ │ ├── TranscriptionOptions.cs │ │ └── OpenAIClientOptions.cs │ └── Gemini │ │ ├── GeminiClientOptions.cs │ │ └── ChatCompletionOptions.cs ├── Models │ ├── FunctionCall.cs │ ├── FunctionResult.cs │ ├── FunctionParameter.cs │ ├── ModerationResult.cs │ ├── ChatFunction.cs │ ├── ChatMessage.cs │ └── Chat.cs ├── Providers │ ├── OpenAI │ │ ├── TextToSpeech.cs │ │ ├── Embeddings.cs │ │ ├── Moderation.cs │ │ └── SpeechRecognition.cs │ └── Gemini │ │ └── ChatCompletion.cs ├── Extensions │ ├── GeminiClientExtension.cs │ └── OpenAIClientExtension.cs ├── ChatAIze.GenerativeCS.csproj └── Clients │ └── GeminiClient.cs ├── ChatAIze.GenerativeCS.Preview ├── icon.png ├── Program.cs └── ChatAIze.GenerativeCS.Preview.csproj ├── ChatAIze.GenerativeCS.slnx ├── tools └── push-nuget.sh ├── CODE_OF_CONDUCT.md └── .gitignore /ChatAIze.GenerativeCS/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chataize/generative-cs/HEAD/ChatAIze.GenerativeCS/icon.png -------------------------------------------------------------------------------- /ChatAIze.GenerativeCS.Preview/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chataize/generative-cs/HEAD/ChatAIze.GenerativeCS.Preview/icon.png -------------------------------------------------------------------------------- /ChatAIze.GenerativeCS.slnx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /ChatAIze.GenerativeCS/Enums/Verbosity.cs: -------------------------------------------------------------------------------- 1 | namespace ChatAIze.GenerativeCS.Enums; 2 | 3 | /// 4 | /// Controls the verbosity level requested from a model. 5 | /// 6 | public enum Verbosity 7 | { 8 | /// 9 | /// Request concise answers. 10 | /// 11 | Low, 12 | 13 | /// 14 | /// Request balanced answers. 15 | /// 16 | Medium, 17 | 18 | /// 19 | /// Request detailed answers. 20 | /// 21 | High 22 | } 23 | -------------------------------------------------------------------------------- /ChatAIze.GenerativeCS/Constants/EnvironmentVariables.cs: -------------------------------------------------------------------------------- 1 | namespace ChatAIze.GenerativeCS.Constants; 2 | 3 | /// 4 | /// Names of environment variables used by the library. 5 | /// 6 | internal static class EnvironmentVariables 7 | { 8 | /// 9 | /// Environment variable that stores the OpenAI API key. 10 | /// 11 | internal const string OpenAIAPIKey = "OPENAI_API_KEY"; 12 | 13 | /// 14 | /// Environment variable that stores the Gemini API key. 15 | /// 16 | internal const string GeminiAPIKey = "GEMINI_API_KEY"; 17 | } 18 | -------------------------------------------------------------------------------- /ChatAIze.GenerativeCS/Enums/VoiceResponseFormat.cs: -------------------------------------------------------------------------------- 1 | namespace ChatAIze.GenerativeCS.Enums; 2 | 3 | /// 4 | /// Determines the container format for generated speech audio. 5 | /// 6 | public enum VoiceResponseFormat 7 | { 8 | /// 9 | /// Use the provider default format. 10 | /// 11 | Default, 12 | /// 13 | /// MPEG-1 Audio Layer III. 14 | /// 15 | MP3, 16 | /// 17 | /// Opus encoded audio. 18 | /// 19 | Opus, 20 | /// 21 | /// Advanced Audio Coding. 22 | /// 23 | AAC, 24 | /// 25 | /// Free Lossless Audio Codec. 26 | /// 27 | FLAC 28 | } 29 | -------------------------------------------------------------------------------- /ChatAIze.GenerativeCS/Enums/ReasoningEffort.cs: -------------------------------------------------------------------------------- 1 | namespace ChatAIze.GenerativeCS.Enums; 2 | 3 | /// 4 | /// Indicates how much reasoning effort the model should apply to a task. 5 | /// 6 | public enum ReasoningEffort 7 | { 8 | /// 9 | /// No reasoning effort applied; behave as a standard generation. 10 | /// 11 | None, 12 | 13 | /// 14 | /// Minimal reasoning effort. 15 | /// 16 | Minimal, 17 | 18 | /// 19 | /// Low reasoning effort. 20 | /// 21 | Low, 22 | 23 | /// 24 | /// Medium reasoning effort. 25 | /// 26 | Medium, 27 | 28 | /// 29 | /// High reasoning effort. 30 | /// 31 | High 32 | } 33 | -------------------------------------------------------------------------------- /ChatAIze.GenerativeCS/Enums/TextToSpeechVoice.cs: -------------------------------------------------------------------------------- 1 | namespace ChatAIze.GenerativeCS.Enums; 2 | 3 | /// 4 | /// Predefined voices supported by text-to-speech synthesis. 5 | /// 6 | public enum TextToSpeechVoice 7 | { 8 | /// 9 | /// Alloy voice preset. 10 | /// 11 | Alloy, 12 | /// 13 | /// Echo voice preset. 14 | /// 15 | Echo, 16 | /// 17 | /// Fable voice preset. 18 | /// 19 | Fable, 20 | /// 21 | /// Onyx voice preset. 22 | /// 23 | Onyx, 24 | /// 25 | /// Nova voice preset. 26 | /// 27 | Nova, 28 | /// 29 | /// Shimmer voice preset. 30 | /// 31 | Shimmer 32 | } 33 | -------------------------------------------------------------------------------- /ChatAIze.GenerativeCS/Enums/TranscriptionResponseFormat.cs: -------------------------------------------------------------------------------- 1 | namespace ChatAIze.GenerativeCS.Enums; 2 | 3 | /// 4 | /// Supported output formats for transcription and translation responses. 5 | /// 6 | public enum TranscriptionResponseFormat 7 | { 8 | /// 9 | /// JSON response payload. 10 | /// 11 | Json, 12 | /// 13 | /// Plain text response payload. 14 | /// 15 | Text, 16 | /// 17 | /// SubRip subtitle format. 18 | /// 19 | SRT, 20 | /// 21 | /// Verbose JSON response payload including timestamps. 22 | /// 23 | VerboseJson, 24 | /// 25 | /// Web Video Text Tracks subtitle format. 26 | /// 27 | VTT 28 | } 29 | -------------------------------------------------------------------------------- /tools/push-nuget.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | 4 | cd ../ChatAIze.GenerativeCS 5 | dotnet publish 6 | dotnet pack 7 | 8 | nupkg=$(ls ./bin/Release/ChatAIze.GenerativeCS.*.nupkg | grep -v '\.snupkg$' | sort -V | tail -n1) 9 | snupkg="${nupkg%.nupkg}.snupkg" 10 | 11 | dotnet nuget push "$nupkg" --api-key "$CHATAIZE_NUGET_API_KEY" --source "https://api.nuget.org/v3/index.json" --skip-duplicate 12 | dotnet nuget push "$snupkg" --api-key "$CHATAIZE_NUGET_API_KEY" --source "https://api.nuget.org/v3/index.json" --skip-duplicate 13 | dotnet nuget push "$nupkg" --api-key "$GITHUB_PAT" --source "https://nuget.pkg.github.com/chataize/index.json" --skip-duplicate 14 | dotnet nuget push "$snupkg" --api-key "$GITHUB_PAT" --source "https://nuget.pkg.github.com/chataize/index.json" --skip-duplicate 15 | -------------------------------------------------------------------------------- /ChatAIze.GenerativeCS/Constants/TextToSpeechModels.cs: -------------------------------------------------------------------------------- 1 | namespace ChatAIze.GenerativeCS.Constants; 2 | 3 | /// 4 | /// Supported text-to-speech model identifiers. 5 | /// 6 | public static class TextToSpeechModels 7 | { 8 | /// 9 | /// OpenAI text-to-speech models. 10 | /// 11 | public static class OpenAI 12 | { 13 | /// 14 | /// Text-to-speech model tts-1. 15 | /// 16 | public const string TTS1 = "tts-1"; 17 | 18 | /// 19 | /// High-definition text-to-speech model tts-1-hd. 20 | /// 21 | public const string TTS1HD = "tts-1-hd"; 22 | 23 | /// 24 | /// GPT-4o-mini text-to-speech model identifier. 25 | /// 26 | public const string GPT4oMiniTTS = "gpt-4o-mini-tts"; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /ChatAIze.GenerativeCS/Constants/SpeechRecognitionModels.cs: -------------------------------------------------------------------------------- 1 | namespace ChatAIze.GenerativeCS.Constants; 2 | 3 | /// 4 | /// Supported speech recognition model identifiers. 5 | /// 6 | public static class SpeechRecognitionModels 7 | { 8 | /// 9 | /// OpenAI speech recognition models. 10 | /// 11 | public static class OpenAI 12 | { 13 | /// 14 | /// Whisper v1 model identifier. 15 | /// 16 | public const string Whisper1 = "whisper-1"; 17 | 18 | /// 19 | /// GPT-4o transcription model identifier. 20 | /// 21 | public const string GPT4oTranscribe = "gpt-4o-transcribe"; 22 | 23 | /// 24 | /// GPT-4o-mini transcription model identifier. 25 | /// 26 | public const string GPT4oMiniTranscribe = "gpt-4o-mini-transcribe"; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /ChatAIze.GenerativeCS/Constants/EmbeddingModels.cs: -------------------------------------------------------------------------------- 1 | namespace ChatAIze.GenerativeCS.Constants; 2 | 3 | /// 4 | /// Supported embedding model identifiers. 5 | /// 6 | public static class EmbeddingModels 7 | { 8 | /// 9 | /// OpenAI embedding models. 10 | /// 11 | public static class OpenAI 12 | { 13 | /// 14 | /// Embedding model text-embedding-3-small. 15 | /// 16 | public const string TextEmbedding3Small = "text-embedding-3-small"; 17 | 18 | /// 19 | /// Embedding model text-embedding-3-large. 20 | /// 21 | public const string TextEmbedding3Large = "text-embedding-3-large"; 22 | 23 | /// 24 | /// Legacy embedding model text-embedding-ada-002. 25 | /// 26 | public const string TextEmbeddingAda002 = "text-embedding-ada-002"; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /ChatAIze.GenerativeCS.Preview/Program.cs: -------------------------------------------------------------------------------- 1 | using ChatAIze.GenerativeCS.Clients; 2 | using ChatAIze.GenerativeCS.Constants; 3 | using ChatAIze.GenerativeCS.Enums; 4 | using ChatAIze.GenerativeCS.Models; 5 | using ChatAIze.GenerativeCS.Options.OpenAI; 6 | 7 | var client = new OpenAIClient(); 8 | var chat = new Chat(); 9 | 10 | var options = new ChatCompletionOptions 11 | { 12 | Model = ChatCompletionModels.OpenAI.GPT51, 13 | IsStoringOutputs = true, 14 | IsDebugMode = true, 15 | ReasoningEffort = ReasoningEffort.None, 16 | Verbosity = Verbosity.High 17 | }; 18 | 19 | options.AddFunction("Check City Temperature", (string city) => Random.Shared.Next(0, 100)); 20 | options.AddFunction("Check Country Temperature", async (string country) => 21 | { 22 | await Task.Delay(5000); 23 | return new { Temp = Random.Shared.Next(0, 100) }; 24 | }); 25 | 26 | while (true) 27 | { 28 | var input = Console.ReadLine(); 29 | if (string.IsNullOrWhiteSpace(input)) 30 | { 31 | break; 32 | } 33 | 34 | chat.FromUser(input); 35 | 36 | var response = await client.CompleteAsync(chat, options); 37 | Console.WriteLine(response); 38 | } 39 | -------------------------------------------------------------------------------- /ChatAIze.GenerativeCS/Utilities/EnvironmentVariableManager.cs: -------------------------------------------------------------------------------- 1 | using ChatAIze.GenerativeCS.Constants; 2 | 3 | namespace ChatAIze.GenerativeCS.Utilities; 4 | 5 | /// 6 | /// Provides helpers for retrieving API keys from environment variables. 7 | /// 8 | internal static class EnvironmentVariableManager 9 | { 10 | /// 11 | /// Reads the OpenAI API key from the OPENAI_API_KEY environment variable. 12 | /// 13 | /// The configured API key or null. 14 | internal static string? GetOpenAIAPIKey() 15 | { 16 | // Return null when the variable is unset so callers can supply explicit keys. 17 | return Environment.GetEnvironmentVariable(EnvironmentVariables.OpenAIAPIKey); 18 | } 19 | 20 | /// 21 | /// Reads the Gemini API key from the GEMINI_API_KEY environment variable. 22 | /// 23 | /// The configured API key or null. 24 | internal static string? GetGeminiAPIKey() 25 | { 26 | // Return null when the variable is unset so callers can supply explicit keys. 27 | return Environment.GetEnvironmentVariable(EnvironmentVariables.GeminiAPIKey); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /ChatAIze.GenerativeCS/Constants/ModerationModels.cs: -------------------------------------------------------------------------------- 1 | namespace ChatAIze.GenerativeCS.Constants; 2 | 3 | /// 4 | /// Supported moderation model identifiers. 5 | /// 6 | public static class ModerationModels 7 | { 8 | /// 9 | /// OpenAI moderation models. 10 | /// 11 | public static class OpenAI 12 | { 13 | /// 14 | /// Latest omni moderation model. 15 | /// 16 | public const string OmniModerationLatest = "omni-moderation-latest"; 17 | 18 | /// 19 | /// Omni moderation model dated 2024-09-26. 20 | /// 21 | public const string OmniModeration20240926 = "omni-moderation-2024-09-26"; 22 | 23 | /// 24 | /// Latest text moderation model. 25 | /// 26 | public const string TextModerationLatest = "moderation-latest"; 27 | 28 | /// 29 | /// Stable text moderation model. 30 | /// 31 | public const string TextModerationStable = "text-moderation-stable"; 32 | 33 | /// 34 | /// Text moderation model version 007. 35 | /// 36 | public const string TextModeration007 = "text-moderation-007"; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /ChatAIze.GenerativeCS/Options/OpenAI/ModerationOptions.cs: -------------------------------------------------------------------------------- 1 | using ChatAIze.GenerativeCS.Constants; 2 | 3 | namespace ChatAIze.GenerativeCS.Options.OpenAI; 4 | 5 | /// 6 | /// Configures OpenAI moderation requests, including the model, API key override, and retry policy. 7 | /// 8 | public record ModerationOptions 9 | { 10 | /// 11 | /// Initializes moderation options. 12 | /// 13 | /// Moderation model identifier to send to the provider (defaults to ). 14 | /// Optional API key that overrides the client-level default for moderation calls. 15 | public ModerationOptions(string model = DefaultModels.OpenAI.Moderation, string? apiKey = null) 16 | { 17 | Model = model; 18 | ApiKey = apiKey; 19 | } 20 | 21 | /// 22 | /// Gets or sets the model identifier used for moderation requests (defaults to ). 23 | /// 24 | public string Model { get; set; } = DefaultModels.OpenAI.Moderation; 25 | 26 | /// 27 | /// Gets or sets an optional API key that overrides the client-level key when sending moderation requests. 28 | /// 29 | public string? ApiKey { get; set; } 30 | 31 | /// 32 | /// Gets or sets the maximum number of attempts (including the first call) before a moderation request is treated as failed. 33 | /// 34 | /// Used by the retry helper; does not retry on client-side validation errors. 35 | public int MaxAttempts { get; set; } = 5; 36 | } 37 | -------------------------------------------------------------------------------- /ChatAIze.GenerativeCS/Options/OpenAI/EmbeddingOptions.cs: -------------------------------------------------------------------------------- 1 | using ChatAIze.GenerativeCS.Constants; 2 | 3 | namespace ChatAIze.GenerativeCS.Options.OpenAI; 4 | 5 | /// 6 | /// Configures embedding requests. 7 | /// 8 | public record EmbeddingOptions 9 | { 10 | /// 11 | /// Initializes embedding options. 12 | /// 13 | /// Model identifier used for embeddings. 14 | /// Optional API key overriding the client default. 15 | public EmbeddingOptions(string model = DefaultModels.OpenAI.Embedding, string? apiKey = null) 16 | { 17 | Model = model; 18 | ApiKey = apiKey; 19 | } 20 | 21 | /// 22 | /// Gets or sets the model identifier used for embeddings. 23 | /// 24 | public string Model { get; set; } = DefaultModels.OpenAI.Embedding; 25 | 26 | /// 27 | /// Gets or sets an optional API key that overrides the client-level key. 28 | /// 29 | public string? ApiKey { get; set; } 30 | 31 | /// 32 | /// Gets or sets optional output dimensions. 33 | /// 34 | /// Only supported by specific models; unsupported combinations will be rejected by the provider. 35 | public int? Dimensions { get; set; } 36 | 37 | /// 38 | /// Gets or sets an optional stable end-user identifier passed to the provider for safety, abuse, or rate limiting. 39 | /// 40 | public string? UserTrackingId { get; set; } 41 | 42 | /// 43 | /// Gets or sets the maximum number of retry attempts for a failed request. 44 | /// 45 | public int MaxAttempts { get; set; } = 5; 46 | } 47 | -------------------------------------------------------------------------------- /ChatAIze.GenerativeCS/Utilities/TokenUsageTracker.cs: -------------------------------------------------------------------------------- 1 | namespace ChatAIze.GenerativeCS.Utilities; 2 | 3 | /// 4 | /// Tracks token usage across multiple model requests. 5 | /// 6 | public sealed class TokenUsageTracker 7 | { 8 | /// 9 | /// Gets the number of prompt tokens consumed. 10 | /// 11 | /// Increment-only; caller is responsible for external synchronization if used across threads. 12 | public int PromptTokens { get; private set; } 13 | 14 | /// 15 | /// Gets the number of cached prompt tokens reused. 16 | /// 17 | /// Increment-only; caller is responsible for external synchronization if used across threads. 18 | public int CachedTokens { get; private set; } 19 | 20 | /// 21 | /// Gets the number of completion tokens generated. 22 | /// 23 | /// Increment-only; caller is responsible for external synchronization if used across threads. 24 | public int CompletionTokens { get; private set; } 25 | 26 | /// 27 | /// Increments the prompt token count. 28 | /// 29 | /// Number of tokens to add. 30 | public void AddPromptTokens(int tokens) 31 | { 32 | PromptTokens += tokens; 33 | } 34 | 35 | /// 36 | /// Increments the cached token count. 37 | /// 38 | /// Number of tokens to add. 39 | public void AddCachedTokens(int tokens) 40 | { 41 | CachedTokens += tokens; 42 | } 43 | 44 | /// 45 | /// Increments the completion token count. 46 | /// 47 | /// Number of tokens to add. 48 | public void AddCompletionTokens(int tokens) 49 | { 50 | CompletionTokens += tokens; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /ChatAIze.GenerativeCS/Constants/DefaultModels.cs: -------------------------------------------------------------------------------- 1 | namespace ChatAIze.GenerativeCS.Constants; 2 | 3 | /// 4 | /// Provides default model identifiers used across the library. 5 | /// 6 | public static class DefaultModels 7 | { 8 | /// 9 | /// Default OpenAI model identifiers. 10 | /// 11 | public static class OpenAI 12 | { 13 | /// 14 | /// Default chat completion model. 15 | /// 16 | /// Used when callers omit a model identifier to favor a generally capable, up-to-date model. 17 | public const string ChatCompletion = ChatCompletionModels.OpenAI.GPT51; 18 | 19 | /// 20 | /// Default embedding model. 21 | /// 22 | /// Balances cost and quality for general-purpose embeddings. 23 | public const string Embedding = EmbeddingModels.OpenAI.TextEmbedding3Small; 24 | 25 | /// 26 | /// Default text-to-speech model. 27 | /// 28 | /// Chosen for latency and voice quality in typical scenarios. 29 | public const string TextToSpeech = TextToSpeechModels.OpenAI.GPT4oMiniTTS; 30 | 31 | /// 32 | /// Default speech-to-text model. 33 | /// 34 | public const string SpeechToText = SpeechRecognitionModels.OpenAI.GPT4oTranscribe; 35 | 36 | /// 37 | /// Default moderation model. 38 | /// 39 | public const string Moderation = ModerationModels.OpenAI.OmniModerationLatest; 40 | } 41 | 42 | /// 43 | /// Default Gemini model identifiers. 44 | /// 45 | public static class Gemini 46 | { 47 | /// 48 | /// Default chat completion model. 49 | /// 50 | public const string ChatCompletion = ChatCompletionModels.Gemini.Gemini15Flash; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /ChatAIze.GenerativeCS/Models/FunctionCall.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | using ChatAIze.Abstractions.Chat; 3 | 4 | namespace ChatAIze.GenerativeCS.Models; 5 | 6 | /// 7 | /// Represents a function call issued by a language model. 8 | /// 9 | public record FunctionCall : IFunctionCall 10 | { 11 | /// 12 | /// Initializes an empty function call. 13 | /// 14 | public FunctionCall() { } 15 | 16 | /// 17 | /// Initializes a function call with a name and argument payload. 18 | /// 19 | /// Function name. 20 | /// Arguments as a JSON string. 21 | [SetsRequiredMembers] 22 | public FunctionCall(string name, string arguments) 23 | { 24 | Name = name; 25 | Arguments = arguments; 26 | } 27 | 28 | /// 29 | /// Initializes a function call with a tool call identifier. 30 | /// 31 | /// Provider-specific tool call identifier. 32 | /// Function name. 33 | /// Arguments as a JSON string. 34 | [SetsRequiredMembers] 35 | public FunctionCall(string toolCallId, string name, string arguments) 36 | { 37 | ToolCallId = toolCallId; 38 | Name = name; 39 | Arguments = arguments; 40 | } 41 | 42 | /// 43 | /// Gets or sets the provider-issued tool call identifier. 44 | /// 45 | /// When present, this ties a function call to its matching tool result in later messages. 46 | public string? ToolCallId { get; set; } 47 | 48 | /// 49 | /// Gets or sets the function name. 50 | /// 51 | public string Name { get; set; } = null!; 52 | 53 | /// 54 | /// Gets or sets the serialized argument payload. 55 | /// 56 | public string Arguments { get; set; } = null!; 57 | } 58 | -------------------------------------------------------------------------------- /ChatAIze.GenerativeCS/Options/OpenAI/TranslationOptions.cs: -------------------------------------------------------------------------------- 1 | using ChatAIze.GenerativeCS.Constants; 2 | using ChatAIze.GenerativeCS.Enums; 3 | 4 | namespace ChatAIze.GenerativeCS.Options.OpenAI; 5 | 6 | /// 7 | /// Configures speech translation requests. 8 | /// 9 | public record TranslationOptions 10 | { 11 | /// 12 | /// Initializes translation options. 13 | /// 14 | /// Optional prompt to guide translation. 15 | /// Optional API key overriding the client default. 16 | public TranslationOptions(string? prompt = null, string? apiKey = null) 17 | { 18 | Prompt = prompt; 19 | ApiKey = apiKey; 20 | } 21 | 22 | /// 23 | /// Gets or sets the model identifier used for translation. 24 | /// 25 | public string Model { get; set; } = DefaultModels.OpenAI.SpeechToText; 26 | 27 | /// 28 | /// Gets or sets an optional API key that overrides the client-level key. 29 | /// 30 | public string? ApiKey { get; set; } 31 | 32 | /// 33 | /// Gets or sets an optional prompt to guide translation. 34 | /// 35 | /// Useful for steering tone or providing glossary hints. 36 | public string? Prompt { get; set; } 37 | 38 | /// 39 | /// Gets or sets the sampling temperature. 40 | /// 41 | public double Temperature { get; set; } 42 | 43 | /// 44 | /// Gets or sets the maximum number of retry attempts for a failed request. 45 | /// 46 | public int MaxAttempts { get; set; } = 5; 47 | 48 | /// 49 | /// Gets or sets the desired translation response format. 50 | /// 51 | /// Use JSON variants to retrieve richer metadata; text returns plain translated content. 52 | public TranscriptionResponseFormat ResponseFormat { get; set; } = TranscriptionResponseFormat.Text; 53 | } 54 | -------------------------------------------------------------------------------- /ChatAIze.GenerativeCS/Options/OpenAI/TextToSpeechOptions.cs: -------------------------------------------------------------------------------- 1 | using ChatAIze.GenerativeCS.Constants; 2 | using ChatAIze.GenerativeCS.Enums; 3 | 4 | namespace ChatAIze.GenerativeCS.Options.OpenAI; 5 | 6 | /// 7 | /// Configures text-to-speech synthesis requests. 8 | /// 9 | public record TextToSpeechOptions 10 | { 11 | /// 12 | /// Initializes text-to-speech options. 13 | /// 14 | /// Text-to-speech model identifier. 15 | /// Voice preset. 16 | /// Optional API key overriding the client default. 17 | public TextToSpeechOptions(string model = DefaultModels.OpenAI.TextToSpeech, TextToSpeechVoice voice = TextToSpeechVoice.Alloy, string? apiKey = null) 18 | { 19 | Model = model; 20 | Voice = voice; 21 | ApiKey = apiKey; 22 | } 23 | 24 | /// 25 | /// Gets or sets the model identifier used for synthesis. 26 | /// 27 | public string Model { get; set; } = DefaultModels.OpenAI.TextToSpeech; 28 | 29 | /// 30 | /// Gets or sets the voice preset to use. 31 | /// 32 | public TextToSpeechVoice Voice { get; set; } 33 | 34 | /// 35 | /// Gets or sets an optional API key that overrides the client-level key. 36 | /// 37 | public string? ApiKey { get; set; } 38 | 39 | /// 40 | /// Gets or sets the playback speed multiplier. 41 | /// 42 | /// Values outside provider limits will be rejected; 1.0 keeps natural speed. 43 | public double Speed { get; set; } = 1.0; 44 | 45 | /// 46 | /// Gets or sets the maximum number of retry attempts for a failed request. 47 | /// 48 | public int MaxAttempts { get; set; } = 5; 49 | 50 | /// 51 | /// Gets or sets the desired audio container format. 52 | /// 53 | /// Default provider format is used when left unset. 54 | public VoiceResponseFormat ResponseFormat { get; set; } 55 | } 56 | -------------------------------------------------------------------------------- /ChatAIze.GenerativeCS/Options/OpenAI/TranscriptionOptions.cs: -------------------------------------------------------------------------------- 1 | using ChatAIze.GenerativeCS.Constants; 2 | using ChatAIze.GenerativeCS.Enums; 3 | 4 | namespace ChatAIze.GenerativeCS.Options.OpenAI; 5 | 6 | /// 7 | /// Configures speech-to-text transcription requests. 8 | /// 9 | public record TranscriptionOptions 10 | { 11 | /// 12 | /// Initializes transcription options. 13 | /// 14 | /// Optional expected language hint. 15 | /// Optional API key overriding the client default. 16 | public TranscriptionOptions(string? language = null, string? apiKey = null) 17 | { 18 | Language = language; 19 | ApiKey = apiKey; 20 | } 21 | 22 | /// 23 | /// Gets or sets the model identifier used for transcription. 24 | /// 25 | public string Model { get; set; } = DefaultModels.OpenAI.SpeechToText; 26 | 27 | /// 28 | /// Gets or sets an optional API key that overrides the client-level key. 29 | /// 30 | public string? ApiKey { get; set; } 31 | 32 | /// 33 | /// Gets or sets the expected language of the audio. 34 | /// 35 | public string? Language { get; set; } 36 | 37 | /// 38 | /// Gets or sets an optional prompt to guide transcription. 39 | /// 40 | /// Useful for adding vocabulary hints or formatting guidance. 41 | public string? Prompt { get; set; } 42 | 43 | /// 44 | /// Gets or sets the sampling temperature. 45 | /// 46 | public double Temperature { get; set; } 47 | 48 | /// 49 | /// Gets or sets the maximum number of retry attempts for a failed request. 50 | /// 51 | public int MaxAttempts { get; set; } = 5; 52 | 53 | /// 54 | /// Gets or sets the desired transcription response format. 55 | /// 56 | /// Switch to JSON variants to retrieve timestamps and segment metadata. 57 | public TranscriptionResponseFormat ResponseFormat { get; set; } = TranscriptionResponseFormat.Text; 58 | } 59 | -------------------------------------------------------------------------------- /ChatAIze.GenerativeCS/Models/FunctionResult.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | using ChatAIze.Abstractions.Chat; 3 | 4 | namespace ChatAIze.GenerativeCS.Models; 5 | 6 | /// 7 | /// Represents the serialized value returned to the model after executing a function call. 8 | /// 9 | public record FunctionResult : IFunctionResult 10 | { 11 | /// 12 | /// Initializes an empty function result. 13 | /// 14 | public FunctionResult() { } 15 | 16 | /// 17 | /// Initializes a function result with a name and value. 18 | /// 19 | /// Function name the model requested. 20 | /// Serialized result value (typically JSON or plain text the model can read). 21 | [SetsRequiredMembers] 22 | public FunctionResult(string name, string value) 23 | { 24 | Name = name; 25 | Value = value; 26 | } 27 | 28 | /// 29 | /// Initializes a function result with a tool call identifier. 30 | /// 31 | /// Provider-specific tool call identifier that pairs the result with the originating tool call. 32 | /// Function name. 33 | /// Serialized result value (typically JSON or plain text the model can read). 34 | [SetsRequiredMembers] 35 | public FunctionResult(string toolCallId, string name, string value) 36 | { 37 | ToolCallId = toolCallId; 38 | Name = name; 39 | Value = value; 40 | } 41 | 42 | /// 43 | /// Gets or sets the provider-issued tool call identifier used to correlate the result with a prior tool call. 44 | /// 45 | /// Providers like OpenAI expect this to mirror the tool call id emitted in the original function call. 46 | public string? ToolCallId { get; set; } 47 | 48 | /// 49 | /// Gets or sets the function name requested by the model. 50 | /// 51 | public string Name { get; set; } = null!; 52 | 53 | /// 54 | /// Gets or sets the serialized result value returned to the model. 55 | /// 56 | public string Value { get; set; } = null!; 57 | } 58 | -------------------------------------------------------------------------------- /ChatAIze.GenerativeCS/Options/Gemini/GeminiClientOptions.cs: -------------------------------------------------------------------------------- 1 | using ChatAIze.Abstractions.Chat; 2 | using ChatAIze.GenerativeCS.Models; 3 | 4 | namespace ChatAIze.GenerativeCS.Options.Gemini; 5 | 6 | /// 7 | /// Groups default settings for the . 8 | /// 9 | /// Message type used in the chat. 10 | /// Function call type used in the chat. 11 | /// Function result type used in the chat. 12 | public record GeminiClientOptions 13 | where TMessage : IChatMessage 14 | where TFunctionCall : IFunctionCall 15 | where TFunctionResult : IFunctionResult 16 | { 17 | /// 18 | /// Initializes client options with defaults. 19 | /// 20 | public GeminiClientOptions() { } 21 | 22 | /// 23 | /// Initializes client options with a specific API key. 24 | /// 25 | /// Gemini API key. 26 | public GeminiClientOptions(string? apiKey) 27 | { 28 | ApiKey = apiKey; 29 | } 30 | 31 | /// 32 | /// Gets or sets the API key applied to requests when not overridden per call. 33 | /// 34 | public string? ApiKey { get; set; } 35 | 36 | /// 37 | /// Gets or sets the default chat completion options. 38 | /// 39 | public ChatCompletionOptions DefaultCompletionOptions { get; set; } = new(); 40 | } 41 | 42 | /// 43 | /// Non-generic Gemini client options using the built-in chat models. 44 | /// 45 | public record GeminiClientOptions : GeminiClientOptions 46 | { 47 | /// 48 | /// Initializes client options with defaults. 49 | /// 50 | public GeminiClientOptions() : base() { } 51 | 52 | /// 53 | /// Initializes client options with a specific API key. 54 | /// 55 | /// Gemini API key. 56 | public GeminiClientOptions(string? apiKey) : base(apiKey) { } 57 | } 58 | -------------------------------------------------------------------------------- /ChatAIze.GenerativeCS/Providers/OpenAI/TextToSpeech.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Nodes; 2 | using ChatAIze.GenerativeCS.Enums; 3 | using ChatAIze.GenerativeCS.Options.OpenAI; 4 | using ChatAIze.GenerativeCS.Utilities; 5 | 6 | namespace ChatAIze.GenerativeCS.Providers.OpenAI; 7 | 8 | /// 9 | /// Handles OpenAI text-to-speech synthesis requests. 10 | /// 11 | internal static class TextToSpeech 12 | { 13 | /// 14 | /// Synthesizes speech audio bytes from text. 15 | /// 16 | /// Text to synthesize. 17 | /// API key used for the request. 18 | /// Optional text-to-speech options. 19 | /// HTTP client to use. 20 | /// Cancellation token for the operation. 21 | /// Raw audio bytes. 22 | internal static async Task SynthesizeSpeechAsync(string text, string? apiKey, TextToSpeechOptions? options = null, HttpClient? httpClient = null, CancellationToken cancellationToken = default) 23 | { 24 | options ??= new(); 25 | httpClient ??= new() 26 | { 27 | Timeout = TimeSpan.FromMinutes(15) 28 | }; 29 | 30 | if (!string.IsNullOrWhiteSpace(options.ApiKey)) 31 | { 32 | apiKey = options.ApiKey; 33 | } 34 | 35 | var requestObject = CreateSpeechSynthesisRequest(text, options); 36 | using var response = await httpClient.RepeatPostAsJsonAsync("https://api.openai.com/v1/audio/speech", requestObject, apiKey, options.MaxAttempts, cancellationToken); 37 | 38 | return await response.Content.ReadAsByteArrayAsync(cancellationToken); 39 | } 40 | 41 | /// 42 | /// Builds the JSON payload for a speech synthesis request. 43 | /// 44 | /// Text to synthesize. 45 | /// Text-to-speech options. 46 | /// JSON request payload. 47 | private static JsonObject CreateSpeechSynthesisRequest(string text, TextToSpeechOptions options) 48 | { 49 | var requestObject = new JsonObject 50 | { 51 | ["model"] = options.Model, 52 | ["voice"] = options.Voice.ToString().ToLowerInvariant(), 53 | ["input"] = text 54 | }; 55 | 56 | if (options.Speed != 1.0) 57 | { 58 | requestObject["speed"] = options.Speed; 59 | } 60 | 61 | if (options.ResponseFormat != VoiceResponseFormat.Default) 62 | { 63 | // Skip emitting response_format when using the provider default to keep payload minimal. 64 | requestObject["response_format"] = options.ResponseFormat.ToString().ToLowerInvariant(); 65 | } 66 | 67 | return requestObject; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /ChatAIze.GenerativeCS/Models/FunctionParameter.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | using ChatAIze.Abstractions.Chat; 3 | 4 | namespace ChatAIze.GenerativeCS.Models; 5 | 6 | /// 7 | /// Represents a parameter definition for a callable function. 8 | /// 9 | public record FunctionParameter : IFunctionParameter 10 | { 11 | /// 12 | /// Initializes an empty function parameter definition. 13 | /// 14 | public FunctionParameter() { } 15 | 16 | /// 17 | /// Initializes a parameter definition with a type, name, and optional enum values. 18 | /// 19 | /// Parameter CLR type. 20 | /// Parameter name. 21 | /// True when the parameter must be supplied. 22 | /// Optional allowed values for enum-like parameters. 23 | [SetsRequiredMembers] 24 | public FunctionParameter(Type type, string name, bool isRequired = true, params ICollection enumValues) 25 | { 26 | Type = type; 27 | Name = name; 28 | IsRequired = isRequired; 29 | EnumValues = enumValues; 30 | } 31 | 32 | /// 33 | /// Initializes a parameter definition with a description and optional enum values. 34 | /// 35 | /// Parameter CLR type. 36 | /// Parameter name. 37 | /// Human readable description. 38 | /// True when the parameter must be supplied. 39 | /// Optional allowed values for enum-like parameters. 40 | [SetsRequiredMembers] 41 | public FunctionParameter(Type type, string name, string? description, bool isRequired = true, params ICollection enumValues) 42 | { 43 | Type = type; 44 | Name = name; 45 | Description = description; 46 | IsRequired = isRequired; 47 | EnumValues = enumValues; 48 | } 49 | 50 | /// 51 | /// Gets or sets the CLR type of the parameter. 52 | /// 53 | public required Type Type { get; set; } 54 | 55 | /// 56 | /// Gets or sets the parameter name. 57 | /// 58 | public required string Name { get; set; } 59 | 60 | /// 61 | /// Gets or sets an optional description of the parameter. 62 | /// 63 | public string? Description { get; set; } 64 | 65 | /// 66 | /// Gets or sets a value indicating whether the parameter is required. 67 | /// 68 | public bool IsRequired { get; set; } = true; 69 | 70 | /// 71 | /// Gets or sets enum-like allowed values for the parameter. 72 | /// 73 | public ICollection EnumValues { get; set; } = []; 74 | 75 | IReadOnlyCollection IFunctionParameter.EnumValues => (IReadOnlyCollection)EnumValues; 76 | } 77 | -------------------------------------------------------------------------------- /ChatAIze.GenerativeCS/Options/OpenAI/OpenAIClientOptions.cs: -------------------------------------------------------------------------------- 1 | using ChatAIze.Abstractions.Chat; 2 | using ChatAIze.GenerativeCS.Models; 3 | 4 | namespace ChatAIze.GenerativeCS.Options.OpenAI; 5 | 6 | /// 7 | /// Groups default settings consumed by when per-call overrides are not supplied. 8 | /// 9 | /// Message type used in the chat. 10 | /// Function call type used in the chat. 11 | /// Function result type used in the chat. 12 | public record OpenAIClientOptions 13 | where TMessage : IChatMessage 14 | where TFunctionCall : IFunctionCall 15 | where TFunctionResult : IFunctionResult 16 | { 17 | /// 18 | /// Initializes client options with defaults for each operation type. 19 | /// 20 | public OpenAIClientOptions() { } 21 | 22 | /// 23 | /// Initializes client options with a specific API key. 24 | /// 25 | /// API key to fall back to when a request does not provide one. 26 | public OpenAIClientOptions(string? apiKey = null) 27 | { 28 | ApiKey = apiKey; 29 | } 30 | 31 | /// 32 | /// Gets or sets the API key applied to requests when no request-level override is provided. 33 | /// 34 | public string? ApiKey { get; set; } 35 | 36 | /// 37 | /// Gets or sets the shared chat completion defaults used whenever a request omits explicit options. 38 | /// 39 | public ChatCompletionOptions DefaultCompletionOptions { get; set; } = new(); 40 | 41 | /// 42 | /// Gets or sets the shared embedding defaults used whenever a request omits explicit options. 43 | /// 44 | public EmbeddingOptions DefaultEmbeddingOptions { get; set; } = new(); 45 | 46 | /// 47 | /// Gets or sets the shared text-to-speech defaults used whenever a request omits explicit options. 48 | /// 49 | public TextToSpeechOptions DefaultTextToSpeechOptions { get; set; } = new(); 50 | 51 | /// 52 | /// Gets or sets the shared transcription defaults used whenever a request omits explicit options. 53 | /// 54 | public TranscriptionOptions DefaultTranscriptionOptions { get; set; } = new(); 55 | 56 | /// 57 | /// Gets or sets the shared translation defaults used whenever a request omits explicit options. 58 | /// 59 | public TranslationOptions DefaultTranslationOptions { get; set; } = new(); 60 | 61 | /// 62 | /// Gets or sets the shared moderation defaults used whenever a request omits explicit options. 63 | /// 64 | public ModerationOptions DefaultModerationOptions { get; set; } = new(); 65 | } 66 | 67 | /// 68 | /// Non-generic OpenAI client options using the built-in chat models. 69 | /// 70 | public record OpenAIClientOptions : OpenAIClientOptions 71 | { 72 | /// 73 | /// Initializes client options with defaults for each operation type. 74 | /// 75 | public OpenAIClientOptions() : base() { } 76 | 77 | /// 78 | /// Initializes client options with a specific API key. 79 | /// 80 | /// API key to fall back to when a request does not provide one. 81 | public OpenAIClientOptions(string? apiKey) : base(apiKey) { } 82 | } 83 | -------------------------------------------------------------------------------- /ChatAIze.GenerativeCS.Preview/ChatAIze.GenerativeCS.Preview.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | Generative CS Preview 4 | Generative CS 5 | 0.18.0 6 | ChatAIze 7 | Marcel Kwiatkowski 8 | © ChatAIze 2025 9 | ChatAIze.GenerativeCS.Preview 10 | ChatAIze.GenerativeCS.Preview 11 | ChatAIze.GenerativeCS.Preview 12 | icon.png 13 | README.md 14 | https://www.chataize.com 15 | https://github.com/chataize/generative-cs/releases 16 | GPL-3.0-or-later 17 | git 18 | https://github.com/chataize/generative-cs 19 | true 20 | net10.0 21 | Exe 22 | enable 23 | enable 24 | true 25 | snupkg 26 | true 27 | 28 | Generative AI library for .NET 10.0 with built-in OpenAI ChatGPT and Google Gemini API clients 29 | and support for C# function calling via reflection. 30 | Features: 31 | - Chat Completion 32 | - Response Streaming 33 | - Text Embedding 34 | - Text-to-Speech 35 | - Speech-to-Text 36 | - Moderation 37 | - Configurable Token Limit 38 | - Configurable Character Limit 39 | - Configurable Message Limit 40 | - Message Pinning 41 | - Function Calling 42 | - Support for Dependency Injection 43 | - Automatic Reattempt on Failure 44 | - Advanced Customization 45 | 46 | 47 | ai api api-client api-wrapper artificial-intelligence asp-net asp-net-core aspnet aspnet-core 48 | aspnetcore assistant audio auto automation bard bing bing-chat bot calling chat chat-bot 49 | chat-completion chat-completion-provider chat-gpt chat-gpt-api chataize chatbot chatgpt 50 | chatgpt-api client co co-pilot complete completion completion-generator completion-provider 51 | completions completions-generator completions-provider conversation conversational 52 | conversational-ai copilot cs csharp davinci dialog dotnet dotnet-core embedding 53 | embedding-model embeddings embeddings-model function function-calling functional 54 | functional-gpt functions functions-calling gemini gemini-api gemini-api-client gemini-client 55 | gemini-pro gemini-pro-api gemini-pro-api-client gemini-pro-client generation generative-ai 56 | generative-cs generator google google-bard google-gemini google-gemini-pro 57 | google-gemini-pro-api google-gemini-pro-api-client google-gemini-pro-client gpt gpt-3 gpt-4 58 | gpt-function gpt-functions gpt3 gpt4 kernel language language-model learning library llama llm 59 | machine machine-learning method method-calling methods methods-calling microsot ml model 60 | moderation natural natural-language-processing nlp open-ai open-ai-api open-ai-client openai 61 | openai-api openai-api-client openai-client pilot pro processing prompt provider reflection 62 | respond response response-completion response-generation rest rest-api restful restful-api 63 | robot sdk search semantic sound speech speech-to-text stream streaming synthesis text 64 | text-completion text-embedding text-embeddings text-generation text-synthesis text-to-speech 65 | token transcript transcription transformer transformer-model transformers transformers-model 66 | translation translator tts turbo vector vector-embedding vector-embeddings vector-search 67 | vertex virtual virtual-assistant voice whisper whisper-api wrapper 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /ChatAIze.GenerativeCS/Extensions/GeminiClientExtension.cs: -------------------------------------------------------------------------------- 1 | using ChatAIze.Abstractions.Chat; 2 | using ChatAIze.GenerativeCS.Clients; 3 | using ChatAIze.GenerativeCS.Models; 4 | using ChatAIze.GenerativeCS.Options.Gemini; 5 | using Microsoft.Extensions.DependencyInjection; 6 | 7 | namespace ChatAIze.GenerativeCS.Extensions; 8 | 9 | /// 10 | /// Dependency injection extensions for registering instances. 11 | /// 12 | public static class GeminiClientExtension 13 | { 14 | /// 15 | /// Registers a typed Gemini client using supplied options. 16 | /// 17 | /// Chat container type. 18 | /// Message type. 19 | /// Function call type. 20 | /// Function result type. 21 | /// Service collection to configure. 22 | /// Optional configuration callback. 23 | /// The same service collection. 24 | public static IServiceCollection AddGeminiClient(this IServiceCollection services, Action>? options = null) 25 | where TChat : IChat, new() 26 | where TMessage : IChatMessage, new() 27 | where TFunctionCall : IFunctionCall, new() 28 | where TFunctionResult : IFunctionResult, new() 29 | { 30 | if (options is not null) 31 | { 32 | _ = services.Configure(options); 33 | 34 | if (options is Action options2) 35 | { 36 | _ = services.Configure(options2); 37 | } 38 | } 39 | 40 | _ = services.AddHttpClient>(c => c.Timeout = TimeSpan.FromMinutes(15)); 41 | _ = services.AddHttpClient(c => c.Timeout = TimeSpan.FromMinutes(15)); 42 | _ = services.AddSingleton>(); 43 | _ = services.AddSingleton(); 44 | 45 | return services; 46 | } 47 | 48 | /// 49 | /// Registers the default Gemini client types using supplied options. 50 | /// 51 | /// Service collection to configure. 52 | /// Optional configuration callback. 53 | /// The same service collection. 54 | public static IServiceCollection AddGeminiClient(this IServiceCollection services, Action>? options = null) 55 | { 56 | return services.AddGeminiClient(options); 57 | } 58 | 59 | /// 60 | /// Registers a typed Gemini client with an explicit API key and optional default completion options. 61 | /// 62 | /// Chat container type. 63 | /// Message type. 64 | /// Function call type. 65 | /// Function result type. 66 | /// Service collection to configure. 67 | /// Gemini API key. 68 | /// Optional default completion options. 69 | /// The same service collection. 70 | public static IServiceCollection AddGeminiClient(this IServiceCollection services, string apiKey, ChatCompletionOptions? defaultCompletionOptions = null) 71 | where TChat : IChat, new() 72 | where TMessage : IChatMessage, new() 73 | where TFunctionCall : IFunctionCall, new() 74 | where TFunctionResult : IFunctionResult, new() 75 | { 76 | return services.AddGeminiClient(o => 77 | { 78 | o.ApiKey = apiKey; 79 | 80 | if (defaultCompletionOptions is not null) 81 | { 82 | o.DefaultCompletionOptions = defaultCompletionOptions; 83 | } 84 | }); 85 | } 86 | 87 | /// 88 | /// Registers the default Gemini client types with an explicit API key. 89 | /// 90 | /// Service collection to configure. 91 | /// Gemini API key. 92 | /// The same service collection. 93 | public static IServiceCollection AddGeminiClient(this IServiceCollection services, string apiKey) 94 | { 95 | return services.AddGeminiClient(apiKey); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /ChatAIze.GenerativeCS/ChatAIze.GenerativeCS.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | Generative CS 4 | Generative CS 5 | 0.18.0 6 | ChatAIze 7 | Marcel Kwiatkowski 8 | © ChatAIze 2025 9 | ChatAIze.GenerativeCS 10 | ChatAIze.GenerativeCS 11 | ChatAIze.GenerativeCS 12 | icon.png 13 | README.md 14 | https://www.chataize.com 15 | https://github.com/chataize/generative-cs/releases 16 | GPL-3.0-or-later 17 | git 18 | https://github.com/chataize/generative-cs 19 | true 20 | net10.0 21 | enable 22 | enable 23 | true 24 | true 25 | snupkg 26 | true 27 | 28 | Generative AI library for .NET 10.0 with built-in OpenAI ChatGPT and Google Gemini API clients 29 | and support for C# function calling via reflection. 30 | Features: 31 | - Chat Completion 32 | - Response Streaming 33 | - Text Embedding 34 | - Text-to-Speech 35 | - Speech-to-Text 36 | - Moderation 37 | - Configurable Token Limit 38 | - Configurable Character Limit 39 | - Configurable Message Limit 40 | - Message Pinning 41 | - Function Calling 42 | - Support for Dependency Injection 43 | - Automatic Reattempt on Failure 44 | - Advanced Customization 45 | 46 | 47 | ai api api-client api-wrapper artificial-intelligence asp-net asp-net-core aspnet aspnet-core 48 | aspnetcore assistant audio auto automation bard bing bing-chat bot calling chat chat-bot 49 | chat-completion chat-completion-provider chat-gpt chat-gpt-api chataize chatbot chatgpt 50 | chatgpt-api client co co-pilot complete completion completion-generator completion-provider 51 | completions completions-generator completions-provider conversation conversational 52 | conversational-ai copilot cs csharp davinci dialog dotnet dotnet-core embedding 53 | embedding-model embeddings embeddings-model function function-calling functional 54 | functional-gpt functions functions-calling gemini gemini-api gemini-api-client gemini-client 55 | gemini-pro gemini-pro-api gemini-pro-api-client gemini-pro-client generation generative-ai 56 | generative-cs generator google google-bard google-gemini google-gemini-pro 57 | google-gemini-pro-api google-gemini-pro-api-client google-gemini-pro-client gpt gpt-3 gpt-4 58 | gpt-function gpt-functions gpt3 gpt4 kernel language language-model learning library llama llm 59 | machine machine-learning method method-calling methods methods-calling microsot ml model 60 | moderation natural natural-language-processing nlp open-ai open-ai-api open-ai-client openai 61 | openai-api openai-api-client openai-client pilot pro processing prompt provider reflection 62 | respond response response-completion response-generation rest rest-api restful restful-api 63 | robot sdk search semantic sound speech speech-to-text stream streaming synthesis text 64 | text-completion text-embedding text-embeddings text-generation text-synthesis text-to-speech 65 | token transcript transcription transformer transformer-model transformers transformers-model 66 | translation translator tts turbo vector vector-embedding vector-embeddings vector-search 67 | vertex virtual virtual-assistant voice whisper whisper-api wrapper 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | true 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /ChatAIze.GenerativeCS/Models/ModerationResult.cs: -------------------------------------------------------------------------------- 1 | namespace ChatAIze.GenerativeCS.Models; 2 | 3 | /// 4 | /// Represents category-level flags and scores returned by the moderation endpoint. 5 | /// 6 | /// 7 | /// Boolean properties mirror the provider's category flags; score properties are confidence values between 0 and 1 where higher numbers indicate greater risk. 8 | /// 9 | public record ModerationResult 10 | { 11 | /// 12 | /// Gets or sets a value indicating whether the provider flagged the content for any category. 13 | /// 14 | public bool IsFlagged { get; set; } 15 | 16 | /// 17 | /// Gets or sets whether the provider flagged the content as sexual. 18 | /// 19 | public bool IsSexual { get; set; } 20 | 21 | /// 22 | /// Gets or sets the confidence score (0-1) for sexual content. 23 | /// 24 | public double SexualScore { get; set; } 25 | 26 | /// 27 | /// Gets or sets whether the provider flagged the content as sexual content involving minors. 28 | /// 29 | public bool IsSexualMinors { get; set; } 30 | 31 | /// 32 | /// Gets or sets the confidence score (0-1) for sexual content involving minors. 33 | /// 34 | public double SexualMinorsScore { get; set; } 35 | 36 | /// 37 | /// Gets or sets whether the provider flagged the content as harassment. 38 | /// 39 | public bool IsHarassment { get; set; } 40 | 41 | /// 42 | /// Gets or sets the confidence score (0-1) for harassment. 43 | /// 44 | public double HarassmentScore { get; set; } 45 | 46 | /// 47 | /// Gets or sets whether the provider flagged the content as threatening harassment. 48 | /// 49 | public bool IsHarassmentThreatening { get; set; } 50 | 51 | /// 52 | /// Gets or sets the confidence score (0-1) for threatening harassment. 53 | /// 54 | public double HarassmentThreateningScore { get; set; } 55 | 56 | /// 57 | /// Gets or sets whether the provider flagged the content for hate. 58 | /// 59 | public bool IsHate { get; set; } 60 | 61 | /// 62 | /// Gets or sets the confidence score (0-1) for hate content. 63 | /// 64 | public double HateScore { get; set; } 65 | 66 | /// 67 | /// Gets or sets whether the provider flagged the content for threatening hate. 68 | /// 69 | public bool IsHateThreatening { get; set; } 70 | 71 | /// 72 | /// Gets or sets the confidence score (0-1) for threatening hate content. 73 | /// 74 | public double HateThreateningScore { get; set; } 75 | 76 | /// 77 | /// Gets or sets whether the provider flagged the content as illicit. 78 | /// 79 | public bool IsIllicit { get; set; } 80 | 81 | /// 82 | /// Gets or sets the confidence score (0-1) for illicit content. 83 | /// 84 | public double IllicitScore { get; set; } 85 | 86 | /// 87 | /// Gets or sets whether the provider flagged the content as illicit and violent. 88 | /// 89 | public bool IsIllicitViolent { get; set; } 90 | 91 | /// 92 | /// Gets or sets the confidence score (0-1) for illicit violent content. 93 | /// 94 | public double IllicitViolentScore { get; set; } 95 | 96 | /// 97 | /// Gets or sets whether the provider flagged the content for self-harm. 98 | /// 99 | public bool IsSelfHarm { get; set; } 100 | 101 | /// 102 | /// Gets or sets the confidence score (0-1) for self-harm content. 103 | /// 104 | public double SelfHarmScore { get; set; } 105 | 106 | /// 107 | /// Gets or sets whether the provider flagged the content for self-harm intent. 108 | /// 109 | public bool IsSelfHarmIntent { get; set; } 110 | 111 | /// 112 | /// Gets or sets the confidence score (0-1) for self-harm intent. 113 | /// 114 | public double SelfHarmIntentScore { get; set; } 115 | 116 | /// 117 | /// Gets or sets whether the provider flagged the content for self-harm instructions. 118 | /// 119 | public bool IsSelfHarmInstruction { get; set; } 120 | 121 | /// 122 | /// Gets or sets the confidence score (0-1) for self-harm instructions. 123 | /// 124 | public double SelfHarmInstructionScore { get; set; } 125 | 126 | /// 127 | /// Gets or sets whether the provider flagged the content for violence. 128 | /// 129 | public bool IsViolence { get; set; } 130 | 131 | /// 132 | /// Gets or sets the confidence score (0-1) for violence. 133 | /// 134 | public double ViolenceScore { get; set; } 135 | 136 | /// 137 | /// Gets or sets whether the provider flagged the content for graphic violence. 138 | /// 139 | public bool IsViolenceGraphic { get; set; } 140 | 141 | /// 142 | /// Gets or sets the confidence score (0-1) for graphic violence. 143 | /// 144 | public double ViolenceGraphicScore { get; set; } 145 | } 146 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | contact@chataize.com. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /ChatAIze.GenerativeCS/Providers/OpenAI/Embeddings.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | using System.Text.Json.Nodes; 3 | using ChatAIze.GenerativeCS.Options.OpenAI; 4 | using ChatAIze.GenerativeCS.Utilities; 5 | 6 | namespace ChatAIze.GenerativeCS.Providers.OpenAI; 7 | 8 | /// 9 | /// Handles OpenAI embedding requests. 10 | /// 11 | internal static class Embeddings 12 | { 13 | /// 14 | /// Requests an embedding vector for the given text. 15 | /// 16 | /// Text to embed. 17 | /// API key used for the request. 18 | /// Optional embedding options. 19 | /// Optional tracker for prompt tokens. 20 | /// HTTP client to use. 21 | /// Cancellation token for the operation. 22 | /// Embedding vector as an array of floats. 23 | internal static async Task GetEmbeddingAsync(string text, string? apiKey, EmbeddingOptions? options = null, TokenUsageTracker? usageTracker = null, HttpClient? httpClient = null, CancellationToken cancellationToken = default) 24 | { 25 | options ??= new(); 26 | httpClient ??= new() 27 | { 28 | Timeout = TimeSpan.FromMinutes(15) 29 | }; 30 | 31 | if (!string.IsNullOrWhiteSpace(options.ApiKey)) 32 | { 33 | apiKey = options.ApiKey; 34 | } 35 | 36 | var request = CreateEmbeddingRequest(text, false, options); 37 | 38 | using var response = await httpClient.RepeatPostAsJsonAsync("https://api.openai.com/v1/embeddings", request, apiKey, options.MaxAttempts, cancellationToken); 39 | using var responseContent = await response.Content.ReadAsStreamAsync(cancellationToken); 40 | using var responseDocument = await JsonDocument.ParseAsync(responseContent, cancellationToken: cancellationToken); 41 | 42 | if (usageTracker is not null) 43 | { 44 | var usage = responseDocument.RootElement.GetProperty("usage"); 45 | var promptTokens = usage.GetProperty("prompt_tokens").GetInt32(); 46 | 47 | usageTracker.AddPromptTokens(promptTokens); 48 | } 49 | 50 | var embedding = new List(); 51 | foreach (var element in responseDocument.RootElement.GetProperty("data")[0].GetProperty("embedding").EnumerateArray()) 52 | { 53 | embedding.Add(element.GetSingle()); 54 | } 55 | 56 | return [.. embedding]; 57 | } 58 | 59 | /// 60 | /// Requests an embedding encoded as Base64 for the given text. 61 | /// 62 | /// Text to embed. 63 | /// API key used for the request. 64 | /// Optional embedding options. 65 | /// Optional tracker for prompt tokens. 66 | /// HTTP client to use. 67 | /// Cancellation token for the operation. 68 | /// Embedding encoded as a Base64 string. 69 | internal static async Task GetBase64EmbeddingAsync(string text, string? apiKey = null, EmbeddingOptions? options = null, TokenUsageTracker? usageTracker = null, HttpClient? httpClient = null, CancellationToken cancellationToken = default) 70 | { 71 | options ??= new(); 72 | httpClient ??= new() 73 | { 74 | Timeout = TimeSpan.FromMinutes(15) 75 | }; 76 | 77 | if (!string.IsNullOrWhiteSpace(options.ApiKey)) 78 | { 79 | apiKey = options.ApiKey; 80 | } 81 | 82 | var request = CreateEmbeddingRequest(text, true, options); 83 | 84 | using var response = await httpClient.RepeatPostAsJsonAsync("https://api.openai.com/v1/embeddings", request, apiKey, options.MaxAttempts, cancellationToken); 85 | using var responseContent = await response.Content.ReadAsStreamAsync(cancellationToken); 86 | using var responseDocument = await JsonDocument.ParseAsync(responseContent, cancellationToken: cancellationToken); 87 | 88 | if (usageTracker is not null) 89 | { 90 | var usage = responseDocument.RootElement.GetProperty("usage"); 91 | var promptTokens = usage.GetProperty("prompt_tokens").GetInt32(); 92 | 93 | usageTracker.AddPromptTokens(promptTokens); 94 | } 95 | 96 | return responseDocument.RootElement.GetProperty("data")[0].GetProperty("embedding").GetString()!; 97 | } 98 | 99 | /// 100 | /// Builds the JSON payload for an embedding request. 101 | /// 102 | /// Text to embed. 103 | /// True to request Base64 output. 104 | /// Embedding options. 105 | /// JSON request payload. 106 | private static JsonObject CreateEmbeddingRequest(string text, bool isBase64Format, EmbeddingOptions options) 107 | { 108 | var request = new JsonObject 109 | { 110 | { "input", text }, 111 | { "model", options.Model } 112 | }; 113 | 114 | if (isBase64Format) 115 | { 116 | request.Add("encoding_format", "base64"); 117 | } 118 | 119 | if (options.Dimensions.HasValue) 120 | { 121 | request.Add("dimensions", options.Dimensions); 122 | } 123 | 124 | if (options.UserTrackingId is not null) 125 | { 126 | request.Add("user", options.UserTrackingId); 127 | } 128 | 129 | // OpenAI expects encoding_format to be omitted unless explicitly requesting base64. 130 | return request; 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /ChatAIze.GenerativeCS/Providers/OpenAI/Moderation.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | using System.Text.Json.Nodes; 3 | using ChatAIze.GenerativeCS.Constants; 4 | using ChatAIze.GenerativeCS.Models; 5 | using ChatAIze.GenerativeCS.Options.OpenAI; 6 | using ChatAIze.GenerativeCS.Utilities; 7 | 8 | namespace ChatAIze.GenerativeCS.Providers.OpenAI; 9 | 10 | /// 11 | /// Handles OpenAI content moderation requests and converts responses into strongly typed results. 12 | /// 13 | internal static class Moderation 14 | { 15 | /// 16 | /// Moderates a piece of text using the specified options. 17 | /// 18 | /// Raw text to evaluate for safety issues. 19 | /// API key used for the request when not overridden by . 20 | /// Optional moderation options; defaults are applied when not provided. 21 | /// HTTP client to use. When null, a client with a 15-minute timeout is created. 22 | /// Cancellation token for the operation. 23 | /// Structured moderation result. 24 | internal static async Task ModerateAsync(string text, string? apiKey, ModerationOptions? options = null, HttpClient? httpClient = null, CancellationToken cancellationToken = default) 25 | { 26 | options ??= new(); 27 | httpClient ??= new() 28 | { 29 | Timeout = TimeSpan.FromMinutes(15) 30 | }; 31 | 32 | if (!string.IsNullOrWhiteSpace(options.ApiKey)) 33 | { 34 | apiKey = options.ApiKey; 35 | } 36 | 37 | var request = CreateModerationRequest(text, options); 38 | 39 | using var response = await httpClient.RepeatPostAsJsonAsync("https://api.openai.com/v1/moderations", request, apiKey, options.MaxAttempts, cancellationToken); 40 | using var responseStream = await response.Content.ReadAsStreamAsync(cancellationToken); 41 | using var responseDocument = await JsonDocument.ParseAsync(responseStream, cancellationToken: cancellationToken); 42 | 43 | return ParseModerationResponse(responseDocument); 44 | } 45 | 46 | /// 47 | /// Builds the JSON payload for a moderation request. 48 | /// 49 | /// Text to moderate. 50 | /// Moderation options. 51 | /// JSON request payload. 52 | private static JsonObject CreateModerationRequest(string text, ModerationOptions options) 53 | { 54 | var request = new JsonObject 55 | { 56 | { "input", text } 57 | }; 58 | 59 | if (options.Model != ModerationModels.OpenAI.TextModerationLatest) 60 | { 61 | // The latest model is implicit; only send when the caller selects a specific version. 62 | request.Add("model", options.Model); 63 | } 64 | 65 | return request; 66 | } 67 | 68 | /// 69 | /// Parses the moderation response into a , reading the first result entry returned by the API. 70 | /// 71 | /// JSON document returned by the API. 72 | /// Structured moderation result. 73 | private static ModerationResult ParseModerationResponse(JsonDocument response) 74 | { 75 | var result = response.RootElement.GetProperty("results")[0]; 76 | var categories = result.GetProperty("categories"); 77 | var categoryScores = result.GetProperty("category_scores"); 78 | 79 | // API may return multiple results, but only the first entry is used here to mirror typical single-input usage. 80 | return new ModerationResult 81 | { 82 | IsFlagged = result.GetProperty("flagged").GetBoolean(), 83 | IsSexual = categories.GetProperty("sexual").GetBoolean(), 84 | SexualScore = categoryScores.GetProperty("sexual").GetDouble(), 85 | IsSexualMinors = categories.GetProperty("sexual/minors").GetBoolean(), 86 | SexualMinorsScore = categoryScores.GetProperty("sexual/minors").GetDouble(), 87 | IsHarassment = categories.GetProperty("harassment").GetBoolean(), 88 | HarassmentScore = categoryScores.GetProperty("harassment").GetDouble(), 89 | IsHarassmentThreatening = categories.GetProperty("harassment/threatening").GetBoolean(), 90 | HarassmentThreateningScore = categoryScores.GetProperty("harassment/threatening").GetDouble(), 91 | IsHate = categories.GetProperty("hate").GetBoolean(), 92 | HateScore = categoryScores.GetProperty("hate").GetDouble(), 93 | IsHateThreatening = categories.GetProperty("hate/threatening").GetBoolean(), 94 | HateThreateningScore = categoryScores.GetProperty("hate/threatening").GetDouble(), 95 | IsIllicit = categories.GetProperty("illicit").GetBoolean(), 96 | IllicitScore = categoryScores.GetProperty("illicit").GetDouble(), 97 | IsIllicitViolent = categories.GetProperty("illicit/violent").GetBoolean(), 98 | IllicitViolentScore = categoryScores.GetProperty("illicit/violent").GetDouble(), 99 | IsSelfHarm = categories.GetProperty("self-harm").GetBoolean(), 100 | SelfHarmScore = categoryScores.GetProperty("self-harm").GetDouble(), 101 | IsSelfHarmIntent = categories.GetProperty("self-harm/intent").GetBoolean(), 102 | SelfHarmIntentScore = categoryScores.GetProperty("self-harm/intent").GetDouble(), 103 | IsSelfHarmInstruction = categories.GetProperty("self-harm/instructions").GetBoolean(), 104 | SelfHarmInstructionScore = categoryScores.GetProperty("self-harm/instructions").GetDouble(), 105 | IsViolence = categories.GetProperty("violence").GetBoolean(), 106 | ViolenceScore = categoryScores.GetProperty("violence").GetDouble(), 107 | IsViolenceGraphic = categories.GetProperty("violence/graphic").GetBoolean(), 108 | ViolenceGraphicScore = categoryScores.GetProperty("violence/graphic").GetDouble(), 109 | }; 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /ChatAIze.GenerativeCS/Providers/OpenAI/SpeechRecognition.cs: -------------------------------------------------------------------------------- 1 | using System.Net.Http.Headers; 2 | using ChatAIze.GenerativeCS.Enums; 3 | using ChatAIze.GenerativeCS.Options.OpenAI; 4 | using ChatAIze.GenerativeCS.Utilities; 5 | 6 | namespace ChatAIze.GenerativeCS.Providers.OpenAI; 7 | 8 | /// 9 | /// Handles OpenAI speech recognition and translation requests. 10 | /// 11 | internal static class SpeechRecognition 12 | { 13 | /// 14 | /// Transcribes audio bytes to text. 15 | /// 16 | /// Audio payload. 17 | /// API key used for the request. 18 | /// Optional transcription options. 19 | /// HTTP client to use. 20 | /// Cancellation token for the operation. 21 | /// Text transcript of the audio. 22 | internal static async Task TranscriptAsync(byte[] audio, string? apiKey, TranscriptionOptions? options = null, HttpClient? httpClient = null, CancellationToken cancellationToken = default) 23 | { 24 | options ??= new(); 25 | httpClient ??= new() 26 | { 27 | Timeout = TimeSpan.FromMinutes(15) 28 | }; 29 | 30 | if (!string.IsNullOrWhiteSpace(options.ApiKey)) 31 | { 32 | apiKey = options.ApiKey; 33 | } 34 | 35 | using var requestContent = CreateTranscriptionRequest(audio, options); 36 | using var response = await httpClient.RepeatPostAsync("https://api.openai.com/v1/audio/transcriptions", requestContent, apiKey, options.MaxAttempts, cancellationToken); 37 | 38 | return await response.Content.ReadAsStringAsync(cancellationToken); 39 | } 40 | 41 | /// 42 | /// Translates audio bytes to English text. 43 | /// 44 | /// Audio payload. 45 | /// API key used for the request. 46 | /// Optional translation options. 47 | /// HTTP client to use. 48 | /// Cancellation token for the operation. 49 | /// Translated text. 50 | internal static async Task TranslateAsync(byte[] audio, string? apiKey, TranslationOptions? options = null, HttpClient? httpClient = null, CancellationToken cancellationToken = default) 51 | { 52 | options ??= new(); 53 | httpClient ??= new() 54 | { 55 | Timeout = TimeSpan.FromMinutes(15) 56 | }; 57 | 58 | if (!string.IsNullOrWhiteSpace(options.ApiKey)) 59 | { 60 | apiKey = options.ApiKey; 61 | } 62 | 63 | using var requestContent = CreateTranslationRequest(audio, options); 64 | using var response = await httpClient.RepeatPostAsync("https://api.openai.com/v1/audio/translations", requestContent, apiKey, options.MaxAttempts, cancellationToken); 65 | 66 | return await response.Content.ReadAsStringAsync(cancellationToken); 67 | } 68 | 69 | /// 70 | /// Builds multipart form data content for a transcription request. 71 | /// 72 | /// Audio payload. 73 | /// Transcription options. 74 | /// Multipart content ready to send. 75 | private static MultipartFormDataContent CreateTranscriptionRequest(byte[] audio, TranscriptionOptions options) 76 | { 77 | var fileContent = new ByteArrayContent(audio); 78 | fileContent.Headers.ContentType = MediaTypeHeaderValue.Parse("multipart/form-data"); 79 | 80 | var content = new MultipartFormDataContent 81 | { 82 | { fileContent, "file", Path.GetFileName("audio.mp3") }, 83 | { new StringContent(options.Model), "model" } 84 | }; 85 | 86 | if (!string.IsNullOrWhiteSpace(options.Language)) 87 | { 88 | content.Add(new StringContent(options.Language), "language"); 89 | } 90 | 91 | if (!string.IsNullOrWhiteSpace(options.Prompt)) 92 | { 93 | content.Add(new StringContent(options.Prompt), "prompt"); 94 | } 95 | 96 | if (options.Temperature != 0) 97 | { 98 | content.Add(new StringContent(options.Temperature.ToString()), "temperature"); 99 | } 100 | 101 | if (options.ResponseFormat != TranscriptionResponseFormat.Json) 102 | { 103 | // Only send response_format when deviating from the default; provider defaults to json. 104 | content.Add(new StringContent(GetResponseFormatName(options.ResponseFormat)), "response_format"); 105 | } 106 | 107 | return content; 108 | } 109 | 110 | /// 111 | /// Builds multipart form data content for a translation request. 112 | /// 113 | /// Audio payload. 114 | /// Translation options. 115 | /// Multipart content ready to send. 116 | private static MultipartFormDataContent CreateTranslationRequest(byte[] audio, TranslationOptions options) 117 | { 118 | var fileContent = new ByteArrayContent(audio); 119 | fileContent.Headers.ContentType = MediaTypeHeaderValue.Parse("multipart/form-data"); 120 | 121 | var content = new MultipartFormDataContent 122 | { 123 | { fileContent, "file", Path.GetFileName("audio.mp3") }, 124 | { new StringContent(options.Model), "model" } 125 | }; 126 | 127 | if (!string.IsNullOrWhiteSpace(options.Prompt)) 128 | { 129 | content.Add(new StringContent(options.Prompt), "prompt"); 130 | } 131 | 132 | if (options.Temperature != 0) 133 | { 134 | content.Add(new StringContent(options.Temperature.ToString()), "temperature"); 135 | } 136 | 137 | if (options.ResponseFormat != TranscriptionResponseFormat.Json) 138 | { 139 | content.Add(new StringContent(GetResponseFormatName(options.ResponseFormat)), "response_format"); 140 | } 141 | 142 | return content; 143 | } 144 | 145 | /// 146 | /// Resolves the provider format name for a transcription response format. 147 | /// 148 | /// Format to convert. 149 | /// Provider format identifier. 150 | private static string GetResponseFormatName(TranscriptionResponseFormat format) 151 | { 152 | if (format == TranscriptionResponseFormat.VerboseJson) 153 | { 154 | return "verbose_json"; 155 | } 156 | 157 | return format.ToString().ToLowerInvariant(); 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /ChatAIze.GenerativeCS/Models/ChatFunction.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | using ChatAIze.Abstractions.Chat; 3 | using ChatAIze.Utilities.Extensions; 4 | 5 | namespace ChatAIze.GenerativeCS.Models; 6 | 7 | /// 8 | /// Describes a callable function that can be surfaced to a language model. 9 | /// 10 | public record ChatFunction : IChatFunction 11 | { 12 | /// 13 | /// Initializes an empty function definition. 14 | /// 15 | public ChatFunction() { } 16 | 17 | /// 18 | /// Initializes a function definition with a name. 19 | /// 20 | /// Function name. 21 | /// True to require a confirmation loop before execution. 22 | [SetsRequiredMembers] 23 | public ChatFunction(string name, bool requiresDoubleCheck = false) 24 | { 25 | Name = name; 26 | RequiresDoubleCheck = requiresDoubleCheck; 27 | Parameters = []; 28 | } 29 | 30 | /// 31 | /// Initializes a function definition with a name and description. 32 | /// 33 | /// Function name. 34 | /// Function description. 35 | /// True to require a confirmation loop before execution. 36 | [SetsRequiredMembers] 37 | public ChatFunction(string name, string? description, bool requiresDoubleCheck = false) 38 | { 39 | Name = name; 40 | Description = description; 41 | RequiresDoubleCheck = requiresDoubleCheck; 42 | Parameters = []; 43 | } 44 | 45 | /// 46 | /// Initializes a function definition that maps directly to a delegate callback. 47 | /// 48 | /// Delegate implementing the function. 49 | [SetsRequiredMembers] 50 | public ChatFunction(Delegate callback) 51 | { 52 | Name = callback.GetNormalizedMethodName(); 53 | Callback = callback; 54 | } 55 | 56 | /// 57 | /// Initializes a function definition with a name mapped to a delegate callback. 58 | /// 59 | /// Function name. 60 | /// Delegate implementing the function. 61 | [SetsRequiredMembers] 62 | public ChatFunction(string name, Delegate callback) 63 | { 64 | Name = name; 65 | Callback = callback; 66 | } 67 | 68 | /// 69 | /// Initializes a function definition with explicit parameters. 70 | /// 71 | /// Function name. 72 | /// Function parameters exposed to the model. 73 | [SetsRequiredMembers] 74 | public ChatFunction(string name, params ICollection parameters) 75 | { 76 | Name = name; 77 | Parameters = parameters; 78 | } 79 | 80 | /// 81 | /// Initializes a function definition with a description and delegate callback. 82 | /// 83 | /// Function name. 84 | /// Function description. 85 | /// Delegate implementing the function. 86 | [SetsRequiredMembers] 87 | public ChatFunction(string name, string? description, Delegate callback) 88 | { 89 | Name = name; 90 | Description = description; 91 | Callback = callback; 92 | } 93 | 94 | /// 95 | /// Initializes a function definition with a description and parameters. 96 | /// 97 | /// Function name. 98 | /// Function description. 99 | /// Function parameters exposed to the model. 100 | [SetsRequiredMembers] 101 | public ChatFunction(string name, string? description, params ICollection parameters) 102 | { 103 | Name = name; 104 | Description = description; 105 | Parameters = parameters; 106 | } 107 | 108 | /// 109 | /// Initializes a function definition that requires double checking and maps to a delegate callback. 110 | /// 111 | /// Function name. 112 | /// True to require a confirmation loop before execution. 113 | /// Delegate implementing the function. 114 | [SetsRequiredMembers] 115 | public ChatFunction(string name, bool requiresDoubleCheck, Delegate callback) 116 | { 117 | Name = name; 118 | RequiresDoubleCheck = requiresDoubleCheck; 119 | Callback = callback; 120 | } 121 | 122 | /// 123 | /// Initializes a function definition that requires double checking and declares parameters. 124 | /// 125 | /// Function name. 126 | /// True to require a confirmation loop before execution. 127 | /// Function parameters exposed to the model. 128 | [SetsRequiredMembers] 129 | public ChatFunction(string name, bool requiresDoubleCheck, params ICollection parameters) 130 | { 131 | Name = name; 132 | RequiresDoubleCheck = requiresDoubleCheck; 133 | Parameters = parameters; 134 | } 135 | 136 | /// 137 | /// Initializes a function definition with a description, confirmation loop, and delegate callback. 138 | /// 139 | /// Function name. 140 | /// Function description. 141 | /// True to require a confirmation loop before execution. 142 | /// Delegate implementing the function. 143 | [SetsRequiredMembers] 144 | public ChatFunction(string name, string? description, bool requiresDoubleCheck, Delegate callback) 145 | { 146 | Name = name; 147 | Description = description; 148 | RequiresDoubleCheck = requiresDoubleCheck; 149 | Callback = callback; 150 | } 151 | 152 | /// 153 | /// Initializes a function definition with a description, confirmation loop, and parameters. 154 | /// 155 | /// Function name. 156 | /// Function description. 157 | /// True to require a confirmation loop before execution. 158 | /// Function parameters exposed to the model. 159 | [SetsRequiredMembers] 160 | public ChatFunction(string name, string? description, bool requiresDoubleCheck, params ICollection parameters) 161 | { 162 | Name = name; 163 | Description = description; 164 | RequiresDoubleCheck = requiresDoubleCheck; 165 | Parameters = parameters; 166 | } 167 | 168 | /// 169 | /// Gets or sets the function name surfaced to the model. 170 | /// 171 | public required string Name { get; set; } 172 | 173 | /// 174 | /// Gets or sets a human readable description of the function. 175 | /// 176 | public string? Description { get; set; } 177 | 178 | /// 179 | /// Gets or sets a value indicating whether the model must double check before executing. 180 | /// 181 | public bool RequiresDoubleCheck { get; set; } 182 | 183 | /// 184 | /// Gets or sets the delegate callback used to execute the function. 185 | /// 186 | public Delegate? Callback { get; set; } 187 | 188 | /// 189 | /// Gets or sets explicit function parameters available to the model. 190 | /// 191 | public ICollection? Parameters { get; set; } 192 | 193 | IReadOnlyCollection? IChatFunction.Parameters => (IReadOnlyCollection?)Parameters; 194 | } 195 | -------------------------------------------------------------------------------- /ChatAIze.GenerativeCS/Utilities/RepeatingHttpClient.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | using System.Net.Http.Headers; 3 | using System.Text; 4 | using System.Text.Encodings.Web; 5 | using System.Text.Json; 6 | 7 | namespace ChatAIze.GenerativeCS.Utilities; 8 | 9 | /// 10 | /// Provides resilient HTTP helpers that retry transient failures. 11 | /// 12 | internal static class RepeatingHttpClient 13 | { 14 | /// 15 | /// JSON serializer options used for outbound payloads. 16 | /// 17 | private static JsonSerializerOptions JsonOptions { get; } = new() 18 | { 19 | Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, 20 | PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower 21 | }; 22 | 23 | /// 24 | /// Backoff schedule applied between retries. 25 | /// 26 | private static readonly TimeSpan[] Delays = [ 27 | TimeSpan.FromSeconds(1), 28 | TimeSpan.FromSeconds(3), 29 | TimeSpan.FromSeconds(5), 30 | TimeSpan.FromSeconds(5), 31 | TimeSpan.FromSeconds(10) 32 | ]; 33 | 34 | /// 35 | /// Posts JSON content with retries and throws on non-success responses. 36 | /// 37 | /// Payload type. 38 | /// HTTP client used for the request. 39 | /// Target URI. 40 | /// Payload to serialize. 41 | /// Optional bearer token. 42 | /// Maximum retry attempts. 43 | /// Cancellation token for the operation. 44 | /// HTTP response message. 45 | internal static async Task RepeatPostAsJsonAsync(this HttpClient client, [StringSyntax("Uri")] string requestUri, TValue value, string? apiKey = null, int maxAttempts = 5, CancellationToken cancellationToken = default) 46 | { 47 | var attempts = 0; 48 | while (true) 49 | { 50 | try 51 | { 52 | var requestContent = JsonSerializer.Serialize(value, JsonOptions); 53 | using var request = new HttpRequestMessage 54 | { 55 | Method = HttpMethod.Post, 56 | RequestUri = new Uri(requestUri), 57 | Content = new StringContent(requestContent, Encoding.UTF8, "application/json") 58 | }; 59 | 60 | if (apiKey is not null) 61 | { 62 | request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", apiKey); 63 | } 64 | 65 | var result = await client.SendAsync(request, cancellationToken); 66 | if (!result.IsSuccessStatusCode) 67 | { 68 | var errorContent = await result.Content.ReadAsStringAsync(cancellationToken); 69 | throw new HttpRequestException($"StatusCode {(int)result.StatusCode}: {errorContent}"); 70 | } 71 | 72 | return result; 73 | } 74 | catch (OperationCanceledException) 75 | { 76 | throw; 77 | } 78 | catch (Exception) 79 | { 80 | if (++attempts >= maxAttempts) 81 | { 82 | throw; 83 | } 84 | 85 | // After we exhaust the predefined backoff windows, reuse the last delay value. 86 | await Task.Delay(Delays[attempts < Delays.Length ? attempts : Delays.Length - 1], cancellationToken); 87 | } 88 | } 89 | } 90 | 91 | /// 92 | /// Posts JSON content expecting a streamed response and retries on transient failures. 93 | /// 94 | /// Payload type. 95 | /// HTTP client used for the request. 96 | /// Target URI. 97 | /// Payload to serialize. 98 | /// Optional bearer token. 99 | /// Maximum retry attempts. 100 | /// Cancellation token for the operation. 101 | /// HTTP response message. 102 | internal static async Task RepeatPostAsJsonForStreamAsync(this HttpClient client, [StringSyntax("Uri")] string requestUri, TValue value, string? apiKey = null, int maxAttempts = 5, CancellationToken cancellationToken = default) 103 | { 104 | var attempts = 0; 105 | while (true) 106 | { 107 | try 108 | { 109 | var requestContent = JsonSerializer.Serialize(value, JsonOptions); 110 | using var request = new HttpRequestMessage 111 | { 112 | Method = HttpMethod.Post, 113 | RequestUri = new Uri(requestUri), 114 | Content = new StringContent(requestContent, Encoding.UTF8, "application/json") 115 | }; 116 | 117 | if (apiKey is not null) 118 | { 119 | request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", apiKey); 120 | } 121 | 122 | var result = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken); 123 | _ = result.EnsureSuccessStatusCode(); 124 | 125 | return result; 126 | } 127 | catch (OperationCanceledException) 128 | { 129 | throw; 130 | } 131 | catch (Exception) 132 | { 133 | if (++attempts >= maxAttempts) 134 | { 135 | throw; 136 | } 137 | 138 | await Task.Delay(Delays[attempts < Delays.Length ? attempts : Delays.Length - 1], cancellationToken); 139 | } 140 | } 141 | } 142 | 143 | /// 144 | /// Posts arbitrary HTTP content with retries and throws on non-success responses. 145 | /// 146 | /// HTTP client used for the request. 147 | /// Target URI. 148 | /// Request content. 149 | /// Optional bearer token. 150 | /// Maximum retry attempts. 151 | /// Cancellation token for the operation. 152 | /// HTTP response message. 153 | internal static async Task RepeatPostAsync(this HttpClient client, [StringSyntax("Uri")] string requestUri, HttpContent content, string? apiKey = null, int maxAttempts = 5, CancellationToken cancellationToken = default) 154 | { 155 | var attempts = 0; 156 | while (true) 157 | { 158 | try 159 | { 160 | using var request = new HttpRequestMessage 161 | { 162 | Method = HttpMethod.Post, 163 | RequestUri = new Uri(requestUri), 164 | Content = content 165 | }; 166 | 167 | if (apiKey is not null) 168 | { 169 | request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", apiKey); 170 | } 171 | 172 | var result = await client.SendAsync(request, cancellationToken); 173 | _ = result.EnsureSuccessStatusCode(); 174 | 175 | return result; 176 | } 177 | catch (OperationCanceledException) 178 | { 179 | throw; 180 | } 181 | catch (Exception) 182 | { 183 | if (++attempts >= maxAttempts) 184 | { 185 | throw; 186 | } 187 | 188 | await Task.Delay(Delays[attempts < Delays.Length ? attempts : Delays.Length - 1], cancellationToken); 189 | } 190 | } 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Ww][Ii][Nn]32/ 27 | [Aa][Rr][Mm]/ 28 | [Aa][Rr][Mm]64/ 29 | bld/ 30 | [Bb]in/ 31 | [Oo]bj/ 32 | [Ll]og/ 33 | [Ll]ogs/ 34 | 35 | # Visual Studio 2015/2017 cache/options directory 36 | .vs/ 37 | # Uncomment if you have tasks that create the project's static files in wwwroot 38 | #wwwroot/ 39 | 40 | # Visual Studio 2017 auto generated files 41 | Generated\ Files/ 42 | 43 | # MSTest test Results 44 | [Tt]est[Rr]esult*/ 45 | [Bb]uild[Ll]og.* 46 | 47 | # NUnit 48 | *.VisualState.xml 49 | TestResult.xml 50 | nunit-*.xml 51 | 52 | # Build Results of an ATL Project 53 | [Dd]ebugPS/ 54 | [Rr]eleasePS/ 55 | dlldata.c 56 | 57 | # Benchmark Results 58 | BenchmarkDotNet.Artifacts/ 59 | 60 | # .NET Core 61 | project.lock.json 62 | project.fragment.lock.json 63 | artifacts/ 64 | 65 | # ASP.NET Scaffolding 66 | ScaffoldingReadMe.txt 67 | 68 | # StyleCop 69 | StyleCopReport.xml 70 | 71 | # Files built by Visual Studio 72 | *_i.c 73 | *_p.c 74 | *_h.h 75 | *.ilk 76 | *.meta 77 | *.obj 78 | *.iobj 79 | *.pch 80 | *.pdb 81 | *.ipdb 82 | *.pgc 83 | *.pgd 84 | *.rsp 85 | *.sbr 86 | *.tlb 87 | *.tli 88 | *.tlh 89 | *.tmp 90 | *.tmp_proj 91 | *_wpftmp.csproj 92 | *.log 93 | *.tlog 94 | *.vspscc 95 | *.vssscc 96 | .builds 97 | *.pidb 98 | *.svclog 99 | *.scc 100 | 101 | # Chutzpah Test files 102 | _Chutzpah* 103 | 104 | # Visual C++ cache files 105 | ipch/ 106 | *.aps 107 | *.ncb 108 | *.opendb 109 | *.opensdf 110 | *.sdf 111 | *.cachefile 112 | *.VC.db 113 | *.VC.VC.opendb 114 | 115 | # Visual Studio profiler 116 | *.psess 117 | *.vsp 118 | *.vspx 119 | *.sap 120 | 121 | # Visual Studio Trace Files 122 | *.e2e 123 | 124 | # TFS 2012 Local Workspace 125 | $tf/ 126 | 127 | # Guidance Automation Toolkit 128 | *.gpState 129 | 130 | # ReSharper is a .NET coding add-in 131 | _ReSharper*/ 132 | *.[Rr]e[Ss]harper 133 | *.DotSettings.user 134 | 135 | # TeamCity is a build add-in 136 | _TeamCity* 137 | 138 | # DotCover is a Code Coverage Tool 139 | *.dotCover 140 | 141 | # AxoCover is a Code Coverage Tool 142 | .axoCover/* 143 | !.axoCover/settings.json 144 | 145 | # Coverlet is a free, cross platform Code Coverage Tool 146 | coverage*.json 147 | coverage*.xml 148 | coverage*.info 149 | 150 | # Visual Studio code coverage results 151 | *.coverage 152 | *.coveragexml 153 | 154 | # NCrunch 155 | _NCrunch_* 156 | .*crunch*.local.xml 157 | nCrunchTemp_* 158 | 159 | # MightyMoose 160 | *.mm.* 161 | AutoTest.Net/ 162 | 163 | # Web workbench (sass) 164 | .sass-cache/ 165 | 166 | # Installshield output folder 167 | [Ee]xpress/ 168 | 169 | # DocProject is a documentation generator add-in 170 | DocProject/buildhelp/ 171 | DocProject/Help/*.HxT 172 | DocProject/Help/*.HxC 173 | DocProject/Help/*.hhc 174 | DocProject/Help/*.hhk 175 | DocProject/Help/*.hhp 176 | DocProject/Help/Html2 177 | DocProject/Help/html 178 | 179 | # Click-Once directory 180 | publish/ 181 | 182 | # Publish Web Output 183 | *.[Pp]ublish.xml 184 | *.azurePubxml 185 | # Note: Comment the next line if you want to checkin your web deploy settings, 186 | # but database connection strings (with potential passwords) will be unencrypted 187 | *.pubxml 188 | *.publishproj 189 | 190 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 191 | # checkin your Azure Web App publish settings, but sensitive information contained 192 | # in these scripts will be unencrypted 193 | PublishScripts/ 194 | 195 | # NuGet Packages 196 | *.nupkg 197 | # NuGet Symbol Packages 198 | *.snupkg 199 | # The packages folder can be ignored because of Package Restore 200 | **/[Pp]ackages/* 201 | # except build/, which is used as an MSBuild target. 202 | !**/[Pp]ackages/build/ 203 | # Uncomment if necessary however generally it will be regenerated when needed 204 | #!**/[Pp]ackages/repositories.config 205 | # NuGet v3's project.json files produces more ignorable files 206 | *.nuget.props 207 | *.nuget.targets 208 | 209 | # Microsoft Azure Build Output 210 | csx/ 211 | *.build.csdef 212 | 213 | # Microsoft Azure Emulator 214 | ecf/ 215 | rcf/ 216 | 217 | # Windows Store app package directories and files 218 | AppPackages/ 219 | BundleArtifacts/ 220 | Package.StoreAssociation.xml 221 | _pkginfo.txt 222 | *.appx 223 | *.appxbundle 224 | *.appxupload 225 | 226 | # Visual Studio cache files 227 | # files ending in .cache can be ignored 228 | *.[Cc]ache 229 | # but keep track of directories ending in .cache 230 | !?*.[Cc]ache/ 231 | 232 | # Others 233 | ClientBin/ 234 | ~$* 235 | *~ 236 | *.dbmdl 237 | *.dbproj.schemaview 238 | *.jfm 239 | *.pfx 240 | *.publishsettings 241 | orleans.codegen.cs 242 | 243 | # Including strong name files can present a security risk 244 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 245 | #*.snk 246 | 247 | # Since there are multiple workflows, uncomment next line to ignore bower_components 248 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 249 | #bower_components/ 250 | 251 | # RIA/Silverlight projects 252 | Generated_Code/ 253 | 254 | # Backup & report files from converting an old project file 255 | # to a newer Visual Studio version. Backup files are not needed, 256 | # because we have git ;-) 257 | _UpgradeReport_Files/ 258 | Backup*/ 259 | UpgradeLog*.XML 260 | UpgradeLog*.htm 261 | ServiceFabricBackup/ 262 | *.rptproj.bak 263 | 264 | # SQL Server files 265 | *.mdf 266 | *.ldf 267 | *.ndf 268 | 269 | # Business Intelligence projects 270 | *.rdl.data 271 | *.bim.layout 272 | *.bim_*.settings 273 | *.rptproj.rsuser 274 | *- [Bb]ackup.rdl 275 | *- [Bb]ackup ([0-9]).rdl 276 | *- [Bb]ackup ([0-9][0-9]).rdl 277 | 278 | # Microsoft Fakes 279 | FakesAssemblies/ 280 | 281 | # GhostDoc plugin setting file 282 | *.GhostDoc.xml 283 | 284 | # Node.js Tools for Visual Studio 285 | .ntvs_analysis.dat 286 | node_modules/ 287 | 288 | # Visual Studio 6 build log 289 | *.plg 290 | 291 | # Visual Studio 6 workspace options file 292 | *.opt 293 | 294 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 295 | *.vbw 296 | 297 | # Visual Studio 6 auto-generated project file (contains which files were open etc.) 298 | *.vbp 299 | 300 | # Visual Studio 6 workspace and project file (working project files containing files to include in project) 301 | *.dsw 302 | *.dsp 303 | 304 | # Visual Studio 6 technical files 305 | *.ncb 306 | *.aps 307 | 308 | # Visual Studio LightSwitch build output 309 | **/*.HTMLClient/GeneratedArtifacts 310 | **/*.DesktopClient/GeneratedArtifacts 311 | **/*.DesktopClient/ModelManifest.xml 312 | **/*.Server/GeneratedArtifacts 313 | **/*.Server/ModelManifest.xml 314 | _Pvt_Extensions 315 | 316 | # Paket dependency manager 317 | .paket/paket.exe 318 | paket-files/ 319 | 320 | # FAKE - F# Make 321 | .fake/ 322 | 323 | # CodeRush personal settings 324 | .cr/personal 325 | 326 | # Python Tools for Visual Studio (PTVS) 327 | __pycache__/ 328 | *.pyc 329 | 330 | # Cake - Uncomment if you are using it 331 | # tools/** 332 | # !tools/packages.config 333 | 334 | # Tabs Studio 335 | *.tss 336 | 337 | # Telerik's JustMock configuration file 338 | *.jmconfig 339 | 340 | # BizTalk build output 341 | *.btp.cs 342 | *.btm.cs 343 | *.odx.cs 344 | *.xsd.cs 345 | 346 | # OpenCover UI analysis results 347 | OpenCover/ 348 | 349 | # Azure Stream Analytics local run output 350 | ASALocalRun/ 351 | 352 | # MSBuild Binary and Structured Log 353 | *.binlog 354 | 355 | # NVidia Nsight GPU debugger configuration file 356 | *.nvuser 357 | 358 | # MFractors (Xamarin productivity tool) working folder 359 | .mfractor/ 360 | 361 | # Local History for Visual Studio 362 | .localhistory/ 363 | 364 | # Visual Studio History (VSHistory) files 365 | .vshistory/ 366 | 367 | # BeatPulse healthcheck temp database 368 | healthchecksdb 369 | 370 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 371 | MigrationBackup/ 372 | 373 | # Ionide (cross platform F# VS Code tools) working folder 374 | .ionide/ 375 | 376 | # Fody - auto-generated XML schema 377 | FodyWeavers.xsd 378 | 379 | # VS Code files for those working on multiple tools 380 | .vscode/* 381 | !.vscode/settings.json 382 | !.vscode/tasks.json 383 | !.vscode/launch.json 384 | !.vscode/extensions.json 385 | *.code-workspace 386 | 387 | # Local History for Visual Studio Code 388 | .history/ 389 | 390 | # Windows Installer files from build outputs 391 | *.cab 392 | *.msi 393 | *.msix 394 | *.msm 395 | *.msp 396 | 397 | # JetBrains Rider 398 | *.sln.iml 399 | 400 | # DS_Store 401 | .DS_Store 402 | -------------------------------------------------------------------------------- /ChatAIze.GenerativeCS/Constants/ChatCompletionModels.cs: -------------------------------------------------------------------------------- 1 | namespace ChatAIze.GenerativeCS.Constants; 2 | 3 | /// 4 | /// Chat completion model identifiers grouped by provider. 5 | /// 6 | public static class ChatCompletionModels 7 | { 8 | /// 9 | /// OpenAI chat completion model identifiers. 10 | /// 11 | public static class OpenAI 12 | { 13 | /// 14 | /// Model identifier for gpt-5.2. 15 | /// 16 | public const string GPT52 = "gpt-5.2"; 17 | 18 | /// 19 | /// Model identifier for gpt-5.2-2025-12-11. 20 | /// 21 | public const string GPT5220251211 = "gpt-5.2-2025-12-11"; 22 | 23 | /// 24 | /// Model identifier for gpt-5.2-chat-latest. 25 | /// 26 | public const string GPT52Chat = "gpt-5.2-chat-latest"; 27 | 28 | /// 29 | /// Model identifier for gpt-5.2-pro. 30 | /// 31 | public const string GPT52Pro = "gpt-5.2-pro"; 32 | 33 | /// 34 | /// Model identifier for gpt-5.2-pro-2025-12-11. 35 | /// 36 | public const string GPT52Pro20251211 = "gpt-5.2-pro-2025-12-11"; 37 | 38 | /// 39 | /// Model identifier for gpt-5.1. 40 | /// 41 | public const string GPT51 = "gpt-5.1"; 42 | 43 | /// 44 | /// Model identifier for gpt-5.1-2025-11-13. 45 | /// 46 | public const string GPT5120251113 = "gpt-5.1-2025-11-13"; 47 | 48 | /// 49 | /// Model identifier for gpt-5. 50 | /// 51 | public const string GPT5 = "gpt-5"; 52 | 53 | /// 54 | /// Model identifier for gpt-5-2025-08-07. 55 | /// 56 | public const string GPT520250807 = "gpt-5-2025-08-07"; 57 | 58 | /// 59 | /// Model identifier for gpt-5-mini. 60 | /// 61 | public const string GPT5Mini = "gpt-5-mini"; 62 | 63 | /// 64 | /// Model identifier for gpt-5-mini-2025-08-07. 65 | /// 66 | public const string GPT5Mini20250807 = "gpt-5-mini-2025-08-07"; 67 | 68 | /// 69 | /// Model identifier for gpt-5-nano. 70 | /// 71 | public const string GPT5Nano = "gpt-5-nano"; 72 | 73 | /// 74 | /// Model identifier for gpt-5-nano-2025-08-07. 75 | /// 76 | public const string GPT5Nano20250807 = "gpt-5-nano-2025-08-07"; 77 | 78 | /// 79 | /// Model identifier for gpt-4.5-preview. 80 | /// 81 | public const string GPT45Preview = "gpt-4.5-preview"; 82 | 83 | /// 84 | /// Model identifier for gpt-4.5-preview-2025-02-27. 85 | /// 86 | public const string GPT45Preview20250227 = "gpt-4.5-preview-2025-02-27"; 87 | 88 | /// 89 | /// Model identifier for gpt-4.1. 90 | /// 91 | public const string GPT41 = "gpt-4.1"; 92 | 93 | /// 94 | /// Model identifier for gpt-4.1-mini. 95 | /// 96 | public const string GPT41Mini = "gpt-4.1-mini"; 97 | 98 | /// 99 | /// Model identifier for gpt-4.1-nano. 100 | /// 101 | public const string GPT41Nano = "gpt-4.1-nano"; 102 | 103 | /// 104 | /// Model identifier for gpt-4o. 105 | /// 106 | public const string GPT4o = "gpt-4o"; 107 | 108 | /// 109 | /// Model identifier for gpt-4o-2024-11-20. 110 | /// 111 | public const string GPT4o20241120 = "gpt-4o-2024-11-20"; 112 | 113 | /// 114 | /// Model identifier for gpt-4o-2024-08-06. 115 | /// 116 | public const string GPT4o20240806 = "gpt-4o-2024-08-06"; 117 | 118 | /// 119 | /// Model identifier for gpt-4o-2024-05-13. 120 | /// 121 | public const string GPT4o20240513 = "gpt-4o-2024-05-13"; 122 | 123 | /// 124 | /// Model identifier for chatgpt-4o-latest. 125 | /// 126 | public const string ChatGPT4oLatest = "chatgpt-4o-latest"; 127 | 128 | /// 129 | /// Model identifier for gpt-4o-mini. 130 | /// 131 | public const string GPT4oMini = "gpt-4o-mini"; 132 | 133 | /// 134 | /// Model identifier for gpt-4o-mini-2024-07-18. 135 | /// 136 | public const string GPT4oMini20240718 = "gpt-4o-mini-2024-07-18"; 137 | 138 | /// 139 | /// Model identifier for o1. 140 | /// 141 | public const string O1 = "o1"; 142 | 143 | /// 144 | /// Model identifier for o1-2024-12-17. 145 | /// 146 | public const string O12024121 = "o1-2024-12-17"; 147 | 148 | /// 149 | /// Model identifier for o1-preview. 150 | /// 151 | public const string O1Preview = "o1-preview"; 152 | 153 | /// 154 | /// Model identifier for o1-preview-2024-09-12. 155 | /// 156 | public const string O1Preview20240912 = "o1-preview-2024-09-12"; 157 | 158 | /// 159 | /// Model identifier for o3-mini. 160 | /// 161 | public const string O3Mini = "o3-mini"; 162 | 163 | /// 164 | /// Model identifier for o3-mini-2025-01-31. 165 | /// 166 | public const string O3Mini20250131 = "o3-mini-2025-01-31"; 167 | 168 | /// 169 | /// Model identifier for o1-mini. 170 | /// 171 | public const string O1Mini = "o1-mini"; 172 | 173 | /// 174 | /// Model identifier for o1-mini-2024-09-12. 175 | /// 176 | public const string O1Mini20240912 = "o1-mini-2024-09-12"; 177 | 178 | /// 179 | /// Model identifier for gpt-4-turbo. 180 | /// 181 | public const string GPT4Turbo = "gpt-4-turbo"; 182 | 183 | /// 184 | /// Model identifier for gpt-4-turbo-2024-04-09. 185 | /// 186 | public const string GPT4Turbo20240409 = "gpt-4-turbo-2024-04-09"; 187 | 188 | /// 189 | /// Model identifier for gpt-4-turbo-preview. 190 | /// 191 | public const string GPT4TurboPreview = "gpt-4-turbo-preview"; 192 | 193 | /// 194 | /// Model identifier for gpt-4-0125-preview. 195 | /// 196 | public const string GPT40125Preview = "gpt-4-0125-preview"; 197 | 198 | /// 199 | /// Model identifier for gpt-4-1106-preview. 200 | /// 201 | public const string GPT41106Preview = "gpt-4-1106-preview"; 202 | 203 | /// 204 | /// Model identifier for gpt-4-vision-preview. 205 | /// 206 | public const string GPT4VisionPreview = "gpt-4-vision-preview"; 207 | 208 | /// 209 | /// Model identifier for gpt-4-1106-vision-preview. 210 | /// 211 | public const string GPT41106VisionPreview = "gpt-4-1106-vision-preview"; 212 | 213 | /// 214 | /// Model identifier for gpt-4. 215 | /// 216 | public const string GPT4 = "gpt-4"; 217 | 218 | /// 219 | /// Model identifier for gpt-4-0613. 220 | /// 221 | public const string GPT40613 = "gpt-4-0613"; 222 | 223 | /// 224 | /// Model identifier for gpt-4-32k. 225 | /// 226 | public const string GPT432k = "gpt-4-32k"; 227 | 228 | /// 229 | /// Model identifier for gpt-4-32k-0613. 230 | /// 231 | public const string GPT432k0613 = "gpt-4-32k-0613"; 232 | 233 | /// 234 | /// Model identifier for gpt-3.5-turbo-0125. 235 | /// 236 | public const string GPT35Turbo0125 = "gpt-3.5-turbo-0125"; 237 | 238 | /// 239 | /// Model identifier for gpt-3.5-turbo. 240 | /// 241 | public const string GPT35Turbo = "gpt-3.5-turbo"; 242 | 243 | /// 244 | /// Model identifier for gpt-3.5-turbo-1106. 245 | /// 246 | public const string GPT35Turbo1106 = "gpt-3.5-turbo-1106"; 247 | 248 | /// 249 | /// Model identifier for gpt-3.5-turbo-instruct. 250 | /// 251 | public const string GPT35TurboInstruct = "gpt-3.5-turbo-instruct"; 252 | 253 | /// 254 | /// Model identifier for gpt-3.5-turbo-16k. 255 | /// 256 | public const string GPT35Turbo16k = "gpt-3.5-turbo-16k"; 257 | 258 | /// 259 | /// Model identifier for gpt-3.5-turbo-0613. 260 | /// 261 | public const string GPT35Turbo0613 = "gpt-3.5-turbo-0613"; 262 | 263 | /// 264 | /// Model identifier for gpt-3.5-turbo-16k-0613. 265 | /// 266 | public const string GPT35Turbo16k0613 = "gpt-3.5-turbo-16k-0613"; 267 | 268 | /// 269 | /// Model identifier for babbage-002. 270 | /// 271 | public const string Babbage002 = "babbage-002"; 272 | 273 | /// 274 | /// Model identifier for davinci-002. 275 | /// 276 | public const string Davinci002 = "davinci-002"; 277 | } 278 | 279 | /// 280 | /// Gemini chat completion model identifiers. 281 | /// 282 | public static class Gemini 283 | { 284 | /// 285 | /// Model identifier for gemini-1.5-pro-002. 286 | /// 287 | public const string Gemini15Pro002 = "gemini-1.5-pro-002"; 288 | 289 | /// 290 | /// Model identifier for gemini-1.5-pro. 291 | /// 292 | public const string Gemini15Pro = "gemini-1.5-pro"; 293 | 294 | /// 295 | /// Model identifier for gemini-1.5-flash. 296 | /// 297 | public const string Gemini15Flash = "gemini-1.5-flash"; 298 | 299 | /// 300 | /// Model identifier for gemini-1.5-flash-002. 301 | /// 302 | public const string Gemini15Flash002 = "gemini-1.5-flash-002"; 303 | 304 | /// 305 | /// Model identifier for gemini-1.5-flash-8b. 306 | /// 307 | public const string Gemini15Flash8B = "gemini-1.5-flash-8b"; 308 | 309 | /// 310 | /// Model identifier for gemma-2-2b-it. 311 | /// 312 | public const string Gemma22B = "gemma-2-2b-it"; 313 | 314 | /// 315 | /// Model identifier for gemma-2-9b-it. 316 | /// 317 | public const string Gemma29B = "gemma-2-9b-it"; 318 | 319 | /// 320 | /// Model identifier for gemma-2-27b-it. 321 | /// 322 | public const string Gemma227B = "gemma-2-27b-it"; 323 | 324 | /// 325 | /// Model identifier for gemini-1.5-pro-exp-0827. 326 | /// 327 | public const string Gemini15ProExperimental0827 = "gemini-1.5-pro-exp-0827"; 328 | 329 | /// 330 | /// Model identifier for gemini-1.5-flash-exp-0827. 331 | /// 332 | public const string Gemini15FlashExperimental0827 = "gemini-1.5-flash-exp-0827"; 333 | 334 | /// 335 | /// Model identifier for gemini-1.5-flash-8b-exp-0924. 336 | /// 337 | public const string Gemini15Flash8BExperimental0924 = "gemini-1.5-flash-8b-exp-0924"; 338 | 339 | /// 340 | /// Model identifier for gemini-1.0-pro. 341 | /// 342 | public const string Gemini10Pro = "gemini-1.0-pro"; 343 | } 344 | } 345 | -------------------------------------------------------------------------------- /ChatAIze.GenerativeCS/Models/ChatMessage.cs: -------------------------------------------------------------------------------- 1 | using ChatAIze.Abstractions.Chat; 2 | 3 | namespace ChatAIze.GenerativeCS.Models; 4 | 5 | /// 6 | /// Represents an individual chat message that may contain text, function calls, function results, or images. 7 | /// 8 | /// Function call type used in the message. 9 | /// Function result type used in the message. 10 | public record ChatMessage : IChatMessage 11 | where TFunctionCall : IFunctionCall 12 | where TFunctionResult : IFunctionResult 13 | { 14 | /// 15 | /// Initializes a new message with default values. 16 | /// 17 | public ChatMessage() { } 18 | 19 | /// 20 | /// Initializes a message with a role, content, and optional images. 21 | /// 22 | /// Role of the sender. 23 | /// Text content of the message. 24 | /// Optional pin location for message ordering. 25 | /// Image URLs attached to the message. 26 | public ChatMessage(ChatRole role, string content, PinLocation pinLocation = PinLocation.None, params ICollection imageUrls) 27 | { 28 | Role = role; 29 | Content = content; 30 | PinLocation = pinLocation; 31 | ImageUrls = imageUrls; 32 | } 33 | 34 | /// 35 | /// Initializes a message with a role, username, content, and optional images. 36 | /// 37 | /// Role of the sender. 38 | /// Optional display name associated with the message (not a stable identifier). 39 | /// Text content of the message. 40 | /// Optional pin location for message ordering. 41 | /// Image URLs attached to the message. 42 | public ChatMessage(ChatRole role, string userName, string content, PinLocation pinLocation = PinLocation.None, params ICollection imageUrls) 43 | { 44 | Role = role; 45 | UserName = userName; 46 | Content = content; 47 | PinLocation = pinLocation; 48 | ImageUrls = imageUrls; 49 | } 50 | 51 | /// 52 | /// Initializes a chatbot message that issues a single function call. 53 | /// 54 | /// Function call issued by the model. 55 | /// Optional pin location for message ordering. 56 | public ChatMessage(TFunctionCall functionCall, PinLocation pinLocation = PinLocation.None) 57 | { 58 | Role = ChatRole.Chatbot; 59 | FunctionCalls = [functionCall]; 60 | PinLocation = pinLocation; 61 | } 62 | 63 | /// 64 | /// Initializes a chatbot message that issues multiple function calls. 65 | /// 66 | /// Function calls issued by the model. 67 | /// Optional pin location for message ordering. 68 | public ChatMessage(ICollection functionCalls, PinLocation pinLocation = PinLocation.None) 69 | { 70 | Role = ChatRole.Chatbot; 71 | FunctionCalls = functionCalls; 72 | PinLocation = pinLocation; 73 | } 74 | 75 | /// 76 | /// Initializes a function result message. 77 | /// 78 | /// Result returned by executing a function. 79 | /// Optional pin location for message ordering. 80 | public ChatMessage(TFunctionResult functionResult, PinLocation pinLocation = PinLocation.None) 81 | { 82 | Role = ChatRole.Function; 83 | FunctionResult = functionResult; 84 | PinLocation = pinLocation; 85 | } 86 | 87 | /// 88 | /// Gets or sets the role associated with the message. 89 | /// 90 | public ChatRole Role { get; set; } 91 | 92 | /// 93 | /// Gets or sets an optional display name associated with the message (not a stable identifier). 94 | /// 95 | public string? UserName { get; set; } 96 | 97 | /// 98 | /// Gets or sets the text content for the message. 99 | /// 100 | public string? Content { get; set; } 101 | 102 | /// 103 | /// Gets or sets function calls produced by the model. 104 | /// 105 | public ICollection FunctionCalls { get; set; } = []; 106 | 107 | /// 108 | /// Gets or sets the function result returned to the model. 109 | /// 110 | public TFunctionResult? FunctionResult { get; set; } 111 | 112 | /// 113 | /// Gets or sets the pin location that affects message ordering. 114 | /// 115 | public PinLocation PinLocation { get; set; } 116 | 117 | /// 118 | /// Gets or sets the UTC creation time of the message. 119 | /// 120 | public DateTimeOffset CreationTime { get; set; } = DateTimeOffset.UtcNow; 121 | 122 | /// 123 | /// Gets or sets image URLs attached to the message. 124 | /// 125 | public ICollection ImageUrls { get; set; } = []; 126 | 127 | /// 128 | /// Creates a system message with the supplied content. 129 | /// 130 | /// System directive. 131 | /// Optional pin location for message ordering. 132 | /// A strongly-typed chat message. 133 | public static IChatMessage FromSystem(string content, PinLocation pinLocation = PinLocation.None) 134 | { 135 | return new ChatMessage(ChatRole.System, content, pinLocation); 136 | } 137 | 138 | /// 139 | /// Creates a user message with the supplied content. 140 | /// 141 | /// User text. 142 | /// Optional pin location for message ordering. 143 | /// A strongly-typed chat message. 144 | public static IChatMessage FromUser(string content, PinLocation pinLocation = PinLocation.None) 145 | { 146 | return new ChatMessage(ChatRole.User, content, pinLocation); 147 | } 148 | 149 | /// 150 | /// Creates a user message with username context. 151 | /// 152 | /// Display name associated with the message (not a stable identifier). 153 | /// User text. 154 | /// Optional pin location for message ordering. 155 | /// A strongly-typed chat message. 156 | public static IChatMessage FromUser(string userName, string content, PinLocation pinLocation = PinLocation.None) 157 | { 158 | return new ChatMessage(ChatRole.User, userName, content, pinLocation); 159 | } 160 | 161 | /// 162 | /// Creates a chatbot message with text content. 163 | /// 164 | /// Message content to assign (parameter name kept for compatibility; represents display text). 165 | /// Optional pin location for message ordering. 166 | /// A strongly-typed chat message. 167 | public static IChatMessage FromChatbot(string userName, PinLocation pinLocation = PinLocation.None) 168 | { 169 | return new ChatMessage(ChatRole.Chatbot, userName, pinLocation); 170 | } 171 | 172 | /// 173 | /// Creates a chatbot message containing a single function call. 174 | /// 175 | /// Function call to include. 176 | /// Optional pin location for message ordering. 177 | /// A strongly-typed chat message. 178 | public static IChatMessage FromChatbot(TFunctionCall functionCall, PinLocation pinLocation = PinLocation.None) 179 | { 180 | return new ChatMessage(functionCall, pinLocation); 181 | } 182 | 183 | /// 184 | /// Creates a chatbot message containing multiple function calls. 185 | /// 186 | /// Function calls to include. 187 | /// Optional pin location for message ordering. 188 | /// A strongly-typed chat message. 189 | public static IChatMessage FromChatbot(ICollection functionCalls, PinLocation pinLocation = PinLocation.None) 190 | { 191 | return new ChatMessage(functionCalls, pinLocation); 192 | } 193 | 194 | /// 195 | /// Creates a message containing a function result. 196 | /// 197 | /// Function result to include. 198 | /// Optional pin location for message ordering. 199 | /// A strongly-typed chat message. 200 | public static IChatMessage FromFunction(TFunctionResult functionResult, PinLocation pinLocation = PinLocation.None) 201 | { 202 | return new ChatMessage(functionResult, pinLocation); 203 | } 204 | } 205 | 206 | /// 207 | /// Non-generic chat message type using the built-in function call and result models. 208 | /// 209 | public record ChatMessage : ChatMessage 210 | { 211 | /// 212 | /// Initializes a new message with default values. 213 | /// 214 | public ChatMessage() : base() { } 215 | 216 | /// 217 | /// Initializes a message with a role and content. 218 | /// 219 | /// Role of the sender. 220 | /// Text content of the message. 221 | /// Optional pin location for message ordering. 222 | public ChatMessage(ChatRole role, string content, PinLocation pinLocation = PinLocation.None) : base(role, content, pinLocation) { } 223 | 224 | /// 225 | /// Initializes a message with a role, display name, and content. 226 | /// 227 | /// Role of the sender. 228 | /// Optional display name associated with the message (not a stable identifier). 229 | /// Text content of the message. 230 | /// Optional pin location for message ordering. 231 | public ChatMessage(ChatRole role, string userName, string content, PinLocation pinLocation = PinLocation.None) : base(role, userName, content, pinLocation) { } 232 | 233 | /// 234 | /// Initializes a chatbot message with a single function call. 235 | /// 236 | /// Function call issued by the model. 237 | /// Optional pin location for message ordering. 238 | public ChatMessage(FunctionCall functionCall, PinLocation pinLocation = PinLocation.None) : base(functionCall, pinLocation) { } 239 | 240 | /// 241 | /// Initializes a chatbot message with multiple function calls. 242 | /// 243 | /// Function calls issued by the model. 244 | /// Optional pin location for message ordering. 245 | public ChatMessage(ICollection functionCalls, PinLocation pinLocation = PinLocation.None) : base(functionCalls, pinLocation) { } 246 | 247 | /// 248 | /// Initializes a message containing a function result. 249 | /// 250 | /// Function result to include. 251 | /// Optional pin location for message ordering. 252 | public ChatMessage(FunctionResult functionResult, PinLocation pinLocation = PinLocation.None) : base(functionResult, pinLocation) { } 253 | } 254 | -------------------------------------------------------------------------------- /ChatAIze.GenerativeCS/Utilities/MessageTools.cs: -------------------------------------------------------------------------------- 1 | using ChatAIze.Abstractions.Chat; 2 | 3 | namespace ChatAIze.GenerativeCS.Utilities; 4 | 5 | /// 6 | /// Helper utilities for preparing chat message payloads. 7 | /// 8 | internal static class MessageTools 9 | { 10 | /// 11 | /// Adds a system message to the beginning of the message list when provided. 12 | /// 13 | /// Message type. 14 | /// Function call type. 15 | /// Function result type. 16 | /// Message list to modify. 17 | /// Optional system message. 18 | internal static void AddDynamicSystemMessage(List messages, string? systemMessage) 19 | where TMessage : IChatMessage, new() 20 | where TFunctionCall : IFunctionCall 21 | where TFunctionResult : IFunctionResult 22 | { 23 | if (string.IsNullOrWhiteSpace(systemMessage)) 24 | { 25 | return; 26 | } 27 | 28 | var firstMessage = new TMessage 29 | { 30 | Role = ChatRole.System, 31 | Content = systemMessage, 32 | PinLocation = PinLocation.Begin 33 | }; 34 | 35 | messages.Insert(0, firstMessage); 36 | } 37 | 38 | /// 39 | /// Appends a time-aware system message to the end of the message list. 40 | /// 41 | /// Message type. 42 | /// Function call type. 43 | /// Function result type. 44 | /// Message list to modify. 45 | /// Current time supplied by the caller. 46 | internal static void AddTimeInformation(List messages, DateTime currentTime) 47 | where TMessage : IChatMessage, new() 48 | where TFunctionCall : IFunctionCall 49 | where TFunctionResult : IFunctionResult 50 | { 51 | var timeMessage = new TMessage 52 | { 53 | Role = ChatRole.System, 54 | Content = $"Current Time: '{currentTime:dddd, MMMM d, yyyy, HH:mm}'.", 55 | PinLocation = PinLocation.End 56 | }; 57 | 58 | messages.Add(timeMessage); 59 | } 60 | 61 | /// 62 | /// Removes messages flagged as unsent or deleted via reflection-based markers. 63 | /// 64 | /// Message type. 65 | /// Function call type. 66 | /// Function result type. 67 | /// Messages to filter. 68 | internal static void RemoveDeletedMessages(IList messages) 69 | where TMessage : IChatMessage, new() 70 | where TFunctionCall : IFunctionCall 71 | where TFunctionResult : IFunctionResult 72 | { 73 | for (var i = messages.Count - 1; i >= 0; i--) 74 | { 75 | var currentMessage = messages[i]; 76 | 77 | var isUnsentProperty = currentMessage.GetType().GetProperty("IsUnsent"); 78 | if (isUnsentProperty is not null && (isUnsentProperty.GetValue(currentMessage) as bool?) == true) 79 | { 80 | // Honor lightweight “soft delete” flags that may exist on provider-specific message types. 81 | messages.RemoveAt(i); 82 | continue; 83 | } 84 | 85 | var isDeletedProperty = currentMessage.GetType().GetProperty("IsDeleted"); 86 | if (isDeletedProperty is not null && (isDeletedProperty.GetValue(currentMessage) as bool?) == true) 87 | { 88 | // Honor lightweight “soft delete” flags that may exist on provider-specific message types. 89 | messages.RemoveAt(i); 90 | continue; 91 | } 92 | } 93 | } 94 | 95 | /// 96 | /// Reorders and trims messages according to the configured message and character limits. 97 | /// 98 | /// Message type. 99 | /// Function call type. 100 | /// Function result type. 101 | /// Messages to mutate. 102 | /// Optional message count limit. 103 | /// Optional character count limit. 104 | internal static void LimitTokens(List messages, int? messageLimit, int? characterLimit) 105 | where TMessage : IChatMessage 106 | where TFunctionCall : IFunctionCall 107 | where TFunctionResult : IFunctionResult 108 | { 109 | // Preserve the ordering contract: explicitly pinned messages stay at the ends and everything 110 | // else keeps its relative order in the middle. 111 | var sortedMessages = messages.Where(m => m.PinLocation == PinLocation.Begin).ToList(); 112 | 113 | sortedMessages.AddRange(messages.Where(m => m.PinLocation is PinLocation.None or PinLocation.Automatic)); 114 | sortedMessages.AddRange(messages.Where(m => m.PinLocation == PinLocation.End)); 115 | 116 | messages.Clear(); 117 | messages.AddRange(sortedMessages); 118 | 119 | var excessiveMessages = messageLimit.HasValue ? messages.Count(m => m.Role != ChatRole.System) - messageLimit : 0; 120 | // Character trimming accounts for both user content and any function result payloads. 121 | var excessiveCharacters = characterLimit.HasValue ? messages.Where(m => m.Role != ChatRole.System).Sum(m => m.Content?.Length ?? 0 + m.FunctionResult?.Value.Length ?? 0) - characterLimit : 0; 122 | 123 | var messagesToRemove = new List(); 124 | foreach (var message in messages) 125 | { 126 | if (excessiveMessages <= 0 && excessiveCharacters <= 0) 127 | { 128 | break; 129 | } 130 | 131 | if (message.Role == ChatRole.System) 132 | { 133 | continue; 134 | } 135 | 136 | if ((excessiveMessages >= 0 || excessiveCharacters >= 0) && message.PinLocation == PinLocation.None) 137 | { 138 | messagesToRemove.Add(message); 139 | 140 | // If message is a function call, remove paired function result: 141 | if (message.FunctionCalls is not null) 142 | { 143 | foreach (var functionCall in message.FunctionCalls) 144 | { 145 | if (string.IsNullOrWhiteSpace(functionCall.ToolCallId)) 146 | { 147 | continue; 148 | } 149 | 150 | foreach (var message2 in messages) 151 | { 152 | if (message2.FunctionResult?.ToolCallId == functionCall.ToolCallId) 153 | { 154 | messagesToRemove.Add(message2); 155 | } 156 | } 157 | } 158 | } 159 | 160 | excessiveMessages--; 161 | excessiveCharacters -= message.Content?.Length ?? 0 + message.FunctionResult?.Value.Length ?? 0; 162 | } 163 | } 164 | 165 | _ = messages.RemoveAll(messagesToRemove.Contains); 166 | } 167 | 168 | /// 169 | /// Removes previous function calls that precede the last user message. 170 | /// 171 | /// Message type. 172 | /// Function call type. 173 | /// Function result type. 174 | /// Messages to mutate. 175 | internal static void RemovePreviousFunctionCalls(List messages) 176 | where TMessage : IChatMessage 177 | where TFunctionCall : IFunctionCall 178 | where TFunctionResult : IFunctionResult 179 | { 180 | var lastNonFunctionMessage = messages.LastOrDefault(m => m.Role == ChatRole.User); 181 | if (lastNonFunctionMessage is null) 182 | { 183 | return; 184 | } 185 | 186 | var lastNonFunctionMessageIndex = messages.IndexOf(lastNonFunctionMessage); 187 | for (var i = lastNonFunctionMessageIndex - 1; i >= 0; i--) 188 | { 189 | var currentMessage = messages[i]; 190 | if (currentMessage.FunctionCalls.Count > 0 || currentMessage.FunctionResult is not null) 191 | { 192 | // Drop tool calls/results that happened before the latest user input so the model 193 | // does not see stale tool interactions when continuing the conversation. 194 | messages.RemoveAt(i); 195 | } 196 | } 197 | } 198 | 199 | /// 200 | /// Converts any system messages to user role messages while preserving content. 201 | /// 202 | /// Message type. 203 | /// Function call type. 204 | /// Function result type. 205 | /// Messages to mutate. 206 | internal static void ReplaceSystemRole(IList messages) 207 | where TMessage : IChatMessage, new() 208 | where TFunctionCall : IFunctionCall 209 | where TFunctionResult : IFunctionResult 210 | { 211 | for (var i = messages.Count - 1; i >= 0; i--) 212 | { 213 | var currentMessage = messages[i]; 214 | if (currentMessage.Role == ChatRole.System) 215 | { 216 | var updatedMessage = new TMessage 217 | { 218 | Role = ChatRole.User, 219 | Content = currentMessage.Content, 220 | FunctionCalls = currentMessage.FunctionCalls, 221 | FunctionResult = currentMessage.FunctionResult, 222 | PinLocation = currentMessage.PinLocation 223 | }; 224 | 225 | messages.RemoveAt(i); 226 | messages.Insert(i, updatedMessage); 227 | } 228 | } 229 | } 230 | 231 | /// 232 | /// Merges consecutive messages from the same sender into a single message. 233 | /// 234 | /// Message type. 235 | /// Function call type. 236 | /// Function result type. 237 | /// Messages to mutate. 238 | internal static void MergeMessages(IList messages) 239 | where TMessage : IChatMessage, new() 240 | where TFunctionCall : IFunctionCall 241 | where TFunctionResult : IFunctionResult 242 | { 243 | for (var i = messages.Count - 1; i >= 1; i--) 244 | { 245 | var previousMessage = messages[i - 1]; 246 | var currentMessage = messages[i]; 247 | 248 | if (previousMessage.Role == currentMessage.Role && previousMessage.UserName == currentMessage.UserName) 249 | { 250 | var replacementMessage = new TMessage 251 | { 252 | Role = previousMessage.Role, 253 | Content = previousMessage.Content, 254 | FunctionCalls = previousMessage.FunctionCalls, 255 | FunctionResult = previousMessage.FunctionResult, 256 | PinLocation = previousMessage.PinLocation 257 | }; 258 | 259 | replacementMessage.Content += $"\n\n{currentMessage.Content}"; 260 | 261 | messages.RemoveAt(i - 1); 262 | messages.Insert(i - 1, replacementMessage); 263 | messages.RemoveAt(i); 264 | } 265 | } 266 | } 267 | } 268 | -------------------------------------------------------------------------------- /ChatAIze.GenerativeCS/Models/Chat.cs: -------------------------------------------------------------------------------- 1 | using ChatAIze.Abstractions.Chat; 2 | 3 | namespace ChatAIze.GenerativeCS.Models; 4 | 5 | /// 6 | /// Represents a chat conversation with typed messages, function calls, and function results. 7 | /// 8 | /// Message type used in the chat. 9 | /// Function call type used in the chat. 10 | /// Function result type used in the chat. 11 | public record Chat : IChat 12 | where TMessage : IChatMessage, new() 13 | where TFunctionCall : IFunctionCall 14 | where TFunctionResult : IFunctionResult 15 | { 16 | /// 17 | /// Initializes a new chat with no messages. 18 | /// 19 | public Chat() { } 20 | 21 | /// 22 | /// Initializes a chat with a single system message. 23 | /// 24 | /// System directive to seed the chat with. 25 | public Chat(string systemMessage) 26 | { 27 | FromSystem(systemMessage); 28 | } 29 | 30 | /// 31 | /// Initializes a chat with a predefined collection of messages. 32 | /// 33 | /// Messages to seed the chat with. 34 | public Chat(IEnumerable messages) 35 | { 36 | Messages = messages.ToList(); 37 | } 38 | 39 | /// 40 | /// Gets or sets an optional stable end-user identifier passed to providers for safety, abuse, or rate limiting. 41 | /// 42 | public string? UserTrackingId { get; set; } 43 | 44 | /// 45 | /// Gets or sets the collection of messages in the chat. 46 | /// 47 | public ICollection Messages { get; set; } = []; 48 | 49 | /// 50 | /// Gets or sets the UTC creation time of the chat. 51 | /// 52 | public DateTimeOffset CreationTime { get; set; } = DateTimeOffset.UtcNow; 53 | 54 | /// 55 | /// Adds a system message to the chat. 56 | /// 57 | /// Content of the system message. 58 | /// Optional pin location for message ordering. 59 | /// The created message. 60 | public ValueTask FromSystemAsync(string message, PinLocation pinLocation = PinLocation.None) 61 | { 62 | var chatMessage = new TMessage 63 | { 64 | Role = ChatRole.System, 65 | Content = message, 66 | PinLocation = pinLocation 67 | }; 68 | 69 | Messages.Add(chatMessage); 70 | return ValueTask.FromResult(chatMessage); 71 | } 72 | 73 | /// 74 | /// Adds a system message to the chat (fire-and-forget wrapper). 75 | /// 76 | /// Content of the system message. 77 | /// Optional pin location for message ordering. 78 | public async void FromSystem(string message, PinLocation pinLocation = PinLocation.None) 79 | { 80 | // Intentional fire-and-forget wrapper to keep API ergonomic for callers that don't await. 81 | _ = await FromSystemAsync(message, pinLocation); 82 | } 83 | 84 | /// 85 | /// Adds a user message with optional image URLs. 86 | /// 87 | /// User text. 88 | /// Optional pin location for message ordering. 89 | /// Optional set of image URLs attached to the message. 90 | /// The created message. 91 | public ValueTask FromUserAsync(string message, PinLocation pinLocation = PinLocation.None, params ICollection imageUrls) 92 | { 93 | var chatMessage = new TMessage 94 | { 95 | Role = ChatRole.User, 96 | Content = message, 97 | PinLocation = pinLocation, 98 | ImageUrls = imageUrls 99 | }; 100 | 101 | Messages.Add(chatMessage); 102 | return ValueTask.FromResult(chatMessage); 103 | } 104 | 105 | /// 106 | /// Adds a user message with optional image URLs (fire-and-forget wrapper). 107 | /// 108 | /// User text. 109 | /// Optional pin location for message ordering. 110 | /// Optional set of image URLs attached to the message. 111 | public async void FromUser(string message, PinLocation pinLocation = PinLocation.None, params ICollection imageUrls) 112 | { 113 | // Intentional fire-and-forget wrapper to keep API ergonomic for callers that don't await. 114 | _ = await FromUserAsync(message, pinLocation, imageUrls); 115 | } 116 | 117 | /// 118 | /// Adds a user message with username context and optional images. 119 | /// 120 | /// Display name of the user the message originates from (not a stable identifier). 121 | /// User text. 122 | /// Optional pin location for message ordering. 123 | /// Optional set of image URLs attached to the message. 124 | /// The created message. 125 | public ValueTask FromUserAsync(string userName, string message, PinLocation pinLocation = PinLocation.None, params ICollection imageUrls) 126 | { 127 | var chatMessage = new TMessage 128 | { 129 | Role = ChatRole.User, 130 | UserName = userName, 131 | Content = message, 132 | PinLocation = pinLocation, 133 | ImageUrls = imageUrls 134 | }; 135 | 136 | Messages.Add(chatMessage); 137 | return ValueTask.FromResult(chatMessage); 138 | } 139 | 140 | /// 141 | /// Adds a user message with username context (fire-and-forget wrapper). 142 | /// 143 | /// Display name of the user the message originates from (not a stable identifier). 144 | /// User text. 145 | /// Optional pin location for message ordering. 146 | /// Optional set of image URLs attached to the message. 147 | public async void FromUser(string userName, string message, PinLocation pinLocation = PinLocation.None, params ICollection imageUrls) 148 | { 149 | // Intentional fire-and-forget wrapper to keep API ergonomic for callers that don't await. 150 | _ = await FromUserAsync(userName, message, pinLocation, imageUrls); 151 | } 152 | 153 | /// 154 | /// Adds a chatbot text response to the chat. 155 | /// 156 | /// Response content. 157 | /// Optional pin location for message ordering. 158 | /// The created message. 159 | public ValueTask FromChatbotAsync(string message, PinLocation pinLocation = PinLocation.None) 160 | { 161 | var chatMessage = new TMessage 162 | { 163 | Role = ChatRole.Chatbot, 164 | Content = message, 165 | PinLocation = pinLocation 166 | }; 167 | 168 | Messages.Add(chatMessage); 169 | return ValueTask.FromResult(chatMessage); 170 | } 171 | 172 | /// 173 | /// Adds a chatbot text response to the chat (fire-and-forget wrapper). 174 | /// 175 | /// Response content. 176 | /// Optional pin location for message ordering. 177 | public async void FromChatbot(string message, PinLocation pinLocation = PinLocation.None) 178 | { 179 | // Intentional fire-and-forget wrapper to keep API ergonomic for callers that don't await. 180 | _ = await FromChatbotAsync(message, pinLocation); 181 | } 182 | 183 | /// 184 | /// Adds a chatbot response containing a single function call. 185 | /// 186 | /// Function call issued by the model. 187 | /// Optional pin location for message ordering. 188 | /// The created message. 189 | public ValueTask FromChatbotAsync(TFunctionCall functionCall, PinLocation pinLocation = PinLocation.None) 190 | { 191 | var chatMessage = new TMessage 192 | { 193 | Role = ChatRole.Chatbot, 194 | FunctionCalls = [functionCall], 195 | PinLocation = pinLocation 196 | }; 197 | 198 | Messages.Add(chatMessage); 199 | return ValueTask.FromResult(chatMessage); 200 | } 201 | 202 | /// 203 | /// Adds a chatbot response containing a single function call (fire-and-forget wrapper). 204 | /// 205 | /// Function call issued by the model. 206 | /// Optional pin location for message ordering. 207 | public async void FromChatbot(TFunctionCall functionCall, PinLocation pinLocation = PinLocation.None) 208 | { 209 | // Intentional fire-and-forget wrapper to keep API ergonomic for callers that don't await. 210 | _ = await FromChatbotAsync(functionCall, pinLocation); 211 | } 212 | 213 | /// 214 | /// Adds a chatbot response that includes multiple function calls. 215 | /// 216 | /// Function calls issued by the model. 217 | /// Optional pin location for message ordering. 218 | /// The created message. 219 | public ValueTask FromChatbotAsync(IEnumerable functionCalls, PinLocation pinLocation = PinLocation.None) 220 | { 221 | var chatMessage = new TMessage 222 | { 223 | Role = ChatRole.Chatbot, 224 | FunctionCalls = functionCalls.ToList(), 225 | PinLocation = pinLocation 226 | }; 227 | 228 | Messages.Add(chatMessage); 229 | return ValueTask.FromResult(chatMessage); 230 | } 231 | 232 | /// 233 | /// Adds a chatbot response that includes multiple function calls (fire-and-forget wrapper). 234 | /// 235 | /// Function calls issued by the model. 236 | /// Optional pin location for message ordering. 237 | public async void FromChatbot(IEnumerable functionCalls, PinLocation pinLocation = PinLocation.None) 238 | { 239 | // Intentional fire-and-forget wrapper to keep API ergonomic for callers that don't await. 240 | _ = await FromChatbotAsync(functionCalls, pinLocation); 241 | } 242 | 243 | /// 244 | /// Adds a function result message to the chat. 245 | /// 246 | /// Result produced by executing a function. 247 | /// Optional pin location for message ordering. 248 | /// The created message. 249 | public ValueTask FromFunctionAsync(TFunctionResult functionResult, PinLocation pinLocation = PinLocation.None) 250 | { 251 | var chatMessage = new TMessage 252 | { 253 | Role = ChatRole.Function, 254 | FunctionResult = functionResult, 255 | PinLocation = pinLocation 256 | }; 257 | 258 | Messages.Add(chatMessage); 259 | return ValueTask.FromResult(chatMessage); 260 | } 261 | 262 | /// 263 | /// Adds a function result message to the chat (fire-and-forget wrapper). 264 | /// 265 | /// Result produced by executing a function. 266 | /// Optional pin location for message ordering. 267 | public async void FromFunction(TFunctionResult functionResult, PinLocation pinLocation = PinLocation.None) 268 | { 269 | // Intentional fire-and-forget wrapper to keep API ergonomic for callers that don't await. 270 | _ = await FromFunctionAsync(functionResult, pinLocation); 271 | } 272 | } 273 | 274 | /// 275 | /// Non-generic chat model using the built-in chat message, function call, and function result types. 276 | /// 277 | public record Chat : Chat 278 | { 279 | /// 280 | /// Initializes a new chat with no messages. 281 | /// 282 | public Chat() : base() { } 283 | 284 | /// 285 | /// Initializes a new chat with a system message. 286 | /// 287 | /// System directive to seed the chat with. 288 | public Chat(string systemMessage) : base(systemMessage) { } 289 | 290 | /// 291 | /// Initializes a new chat with predefined messages. 292 | /// 293 | /// Messages to seed the chat with. 294 | public Chat(IEnumerable messages) : base(messages) { } 295 | } 296 | -------------------------------------------------------------------------------- /ChatAIze.GenerativeCS/Options/Gemini/ChatCompletionOptions.cs: -------------------------------------------------------------------------------- 1 | using ChatAIze.Abstractions.Chat; 2 | using ChatAIze.GenerativeCS.Constants; 3 | using ChatAIze.GenerativeCS.Models; 4 | 5 | namespace ChatAIze.GenerativeCS.Options.Gemini; 6 | 7 | /// 8 | /// Configures chat completion requests for Gemini models. 9 | /// 10 | /// Message type used in the chat. 11 | /// Function call type used in the chat. 12 | /// Function result type used in the chat. 13 | public record ChatCompletionOptions 14 | where TMessage : IChatMessage 15 | where TFunctionCall : IFunctionCall 16 | where TFunctionResult : IFunctionResult 17 | { 18 | /// 19 | /// Initializes Gemini completion options. 20 | /// 21 | /// Model identifier to target. 22 | /// Optional API key overriding the client default. 23 | public ChatCompletionOptions(string model = DefaultModels.Gemini.ChatCompletion, string? apiKey = null) 24 | { 25 | Model = model; 26 | ApiKey = apiKey; 27 | } 28 | 29 | /// 30 | /// Gets or sets the model identifier used for chat completions. 31 | /// 32 | public string Model { get; set; } = DefaultModels.Gemini.ChatCompletion; 33 | 34 | /// 35 | /// Gets or sets an optional API key that overrides the client-level key. 36 | /// 37 | public string? ApiKey { get; set; } 38 | 39 | /// 40 | /// Gets or sets the maximum number of retry attempts for a failed request. 41 | /// 42 | public int MaxAttempts { get; set; } = 5; 43 | 44 | /// 45 | /// Gets or sets the maximum number of non-system messages to include. 46 | /// 47 | public int? MessageLimit { get; set; } 48 | 49 | /// 50 | /// Gets or sets the maximum total character count across user and assistant messages. 51 | /// 52 | public int? CharacterLimit { get; set; } 53 | 54 | /// 55 | /// Gets or sets a value indicating whether time metadata should be appended automatically. 56 | /// 57 | public bool IsTimeAware { get; set; } 58 | 59 | /// 60 | /// Gets or sets a value indicating whether request and response payloads should be logged to the console. 61 | /// 62 | public bool IsDebugMode { get; set; } 63 | 64 | /// 65 | /// Gets or sets functions available to the model. 66 | /// 67 | public List Functions { get; set; } = []; 68 | 69 | /// 70 | /// Gets or sets a callback used to dynamically supply a system message. 71 | /// 72 | /// Applied at request time so callers can inject context-sensitive instructions. 73 | public Func? SystemMessageCallback { get; set; } = null; 74 | 75 | /// 76 | /// Gets or sets a callback that returns the current time when time awareness is enabled. 77 | /// 78 | public Func TimeCallback { get; set; } = () => DateTime.Now; 79 | 80 | /// 81 | /// Gets a callback invoked whenever a message is added to the chat. 82 | /// 83 | /// Useful for logging or persisting transcripts as tool calls and model replies are appended. 84 | public Func AddMessageCallback { get; } = (_) => ValueTask.CompletedTask; 85 | 86 | /// 87 | /// Gets or sets the fallback function callback used when a function does not have an explicit delegate. 88 | /// 89 | /// Defaults to throwing to make the absence of a callback explicit. 90 | public Func> DefaultFunctionCallback { get; set; } = (_, _, _) => throw new NotImplementedException("Function callback has not been implemented."); 91 | 92 | /// 93 | /// Gets or sets an optional function execution context passed to callbacks. 94 | /// 95 | public IFunctionContext? FunctionContext { get; set; } 96 | 97 | /// 98 | /// Adds a prebuilt function definition to the available set. 99 | /// 100 | /// Function metadata to add. 101 | public void AddFunction(IChatFunction function) 102 | { 103 | Functions.Add(function); 104 | } 105 | 106 | /// 107 | /// Adds a function definition by name. 108 | /// 109 | /// Function name. 110 | /// True to require a confirmation loop before execution. 111 | public void AddFunction(string name, bool requiresDoubleCheck = false) 112 | { 113 | Functions.Add(new ChatFunction(name, requiresDoubleCheck)); 114 | } 115 | 116 | /// 117 | /// Adds a function definition by name with a description. 118 | /// 119 | /// Function name. 120 | /// Function description. 121 | /// True to require a confirmation loop before execution. 122 | public void AddFunction(string name, string? description, bool requiresDoubleCheck = false) 123 | { 124 | Functions.Add(new ChatFunction(name, description, requiresDoubleCheck)); 125 | } 126 | 127 | /// 128 | /// Adds a function definition that maps directly to a delegate callback. 129 | /// 130 | /// Delegate implementing the function. 131 | public void AddFunction(Delegate callback) 132 | { 133 | Functions.Add(new ChatFunction(callback)); 134 | } 135 | 136 | /// 137 | /// Adds a function definition with a name and delegate callback. 138 | /// 139 | /// Function name. 140 | /// Delegate implementing the function. 141 | public void AddFunction(string name, Delegate callback) 142 | { 143 | Functions.Add(new ChatFunction(name, callback)); 144 | } 145 | 146 | /// 147 | /// Adds a function definition with explicit parameters. 148 | /// 149 | /// Function name. 150 | /// Function parameters exposed to the model. 151 | public void AddFunction(string name, ICollection parameters) 152 | { 153 | Functions.Add(new ChatFunction(name, parameters)); 154 | } 155 | 156 | /// 157 | /// Adds a function definition with explicit parameters. 158 | /// 159 | /// Function name. 160 | /// Function parameters exposed to the model. 161 | public void AddFunction(string name, params IFunctionParameter[] parameters) 162 | { 163 | Functions.Add(new ChatFunction(name, parameters)); 164 | } 165 | 166 | /// 167 | /// Adds a function definition with a description and delegate callback. 168 | /// 169 | /// Function name. 170 | /// Function description. 171 | /// Delegate implementing the function. 172 | public void AddFunction(string name, string? description, Delegate callback) 173 | { 174 | Functions.Add(new ChatFunction(name, description, callback)); 175 | } 176 | 177 | /// 178 | /// Adds a function definition with a description and explicit parameters. 179 | /// 180 | /// Function name. 181 | /// Function description. 182 | /// Function parameters exposed to the model. 183 | public void AddFunction(string name, string? description, ICollection parameters) 184 | { 185 | Functions.Add(new ChatFunction(name, description, parameters)); 186 | } 187 | 188 | /// 189 | /// Adds a function definition with a description and explicit parameters. 190 | /// 191 | /// Function name. 192 | /// Function description. 193 | /// Function parameters exposed to the model. 194 | public void AddFunction(string name, string? description, params IFunctionParameter[] parameters) 195 | { 196 | Functions.Add(new ChatFunction(name, description, parameters)); 197 | } 198 | 199 | /// 200 | /// Adds a function definition that requires double checking and maps to a delegate callback. 201 | /// 202 | /// Function name. 203 | /// True to require a confirmation loop before execution. 204 | /// Delegate implementing the function. 205 | public void AddFunction(string name, bool requiresDoubleCheck, Delegate callback) 206 | { 207 | Functions.Add(new ChatFunction(name, requiresDoubleCheck, callback)); 208 | } 209 | 210 | /// 211 | /// Adds a function definition that requires double checking and declares parameters. 212 | /// 213 | /// Function name. 214 | /// True to require a confirmation loop before execution. 215 | /// Function parameters exposed to the model. 216 | public void AddFunction(string name, bool requiresDoubleCheck, ICollection parameters) 217 | { 218 | Functions.Add(new ChatFunction(name, requiresDoubleCheck, parameters)); 219 | } 220 | 221 | /// 222 | /// Adds a function definition that requires double checking and declares parameters. 223 | /// 224 | /// Function name. 225 | /// True to require a confirmation loop before execution. 226 | /// Function parameters exposed to the model. 227 | public void AddFunction(string name, bool requiresDoubleCheck, params IFunctionParameter[] parameters) 228 | { 229 | Functions.Add(new ChatFunction(name, requiresDoubleCheck, parameters)); 230 | } 231 | 232 | /// 233 | /// Adds a function definition with description, confirmation loop, and delegate callback. 234 | /// 235 | /// Function name. 236 | /// Function description. 237 | /// True to require a confirmation loop before execution. 238 | /// Delegate implementing the function. 239 | public void AddFunction(string name, string? description, bool requiresDoubleCheck, Delegate callback) 240 | { 241 | Functions.Add(new ChatFunction(name, description, requiresDoubleCheck, callback)); 242 | } 243 | 244 | /// 245 | /// Adds a function definition with description, confirmation loop, and explicit parameters. 246 | /// 247 | /// Function name. 248 | /// Function description. 249 | /// True to require a confirmation loop before execution. 250 | /// Function parameters exposed to the model. 251 | public void AddFunction(string name, string? description, bool requiresDoubleCheck, ICollection parameters) 252 | { 253 | Functions.Add(new ChatFunction(name, description, requiresDoubleCheck, parameters)); 254 | } 255 | 256 | /// 257 | /// Adds a function definition with description, confirmation loop, and explicit parameters. 258 | /// 259 | /// Function name. 260 | /// Function description. 261 | /// True to require a confirmation loop before execution. 262 | /// Function parameters exposed to the model. 263 | public void AddFunction(string name, string? description, bool requiresDoubleCheck, params IFunctionParameter[] parameters) 264 | { 265 | Functions.Add(new ChatFunction(name, description, requiresDoubleCheck, parameters)); 266 | } 267 | 268 | /// 269 | /// Removes a function definition by reference. 270 | /// 271 | /// Function metadata to remove. 272 | /// True when the function was removed. 273 | public bool RemoveFunction(ChatFunction function) 274 | { 275 | return Functions.Remove(function); 276 | } 277 | 278 | /// 279 | /// Removes a function definition by name. 280 | /// 281 | /// Function name to remove. 282 | /// True when the function was removed. 283 | public bool RemoveFunction(string name) 284 | { 285 | var function = Functions.FirstOrDefault(f => f.Name == name); 286 | if (function is null) 287 | { 288 | return false; 289 | } 290 | 291 | return Functions.Remove(function); 292 | } 293 | 294 | /// 295 | /// Removes a function definition that matches the supplied callback. 296 | /// 297 | /// Delegate used to locate the function. 298 | /// True when the function was removed. 299 | public bool RemoveFunction(Delegate callback) 300 | { 301 | var function = Functions.FirstOrDefault(f => f.Callback == callback); 302 | if (function is null) 303 | { 304 | return false; 305 | } 306 | 307 | return Functions.Remove(function); 308 | } 309 | 310 | /// 311 | /// Clears all function definitions. 312 | /// 313 | public void ClearFunctions() 314 | { 315 | Functions.Clear(); 316 | } 317 | } 318 | 319 | /// 320 | /// Non-generic Gemini completion options using the built-in message, function call, and function result types. 321 | /// 322 | public record ChatCompletionOptions : ChatCompletionOptions 323 | { 324 | /// 325 | /// Initializes Gemini completion options. 326 | /// 327 | public ChatCompletionOptions() : base() { } 328 | 329 | /// 330 | /// Initializes Gemini completion options with a specific model identifier. 331 | /// 332 | /// Model identifier to target. 333 | public ChatCompletionOptions(string model = DefaultModels.Gemini.ChatCompletion) : base(model) { } 334 | } 335 | -------------------------------------------------------------------------------- /ChatAIze.GenerativeCS/Extensions/OpenAIClientExtension.cs: -------------------------------------------------------------------------------- 1 | using ChatAIze.Abstractions.Chat; 2 | using ChatAIze.GenerativeCS.Clients; 3 | using ChatAIze.GenerativeCS.Models; 4 | using ChatAIze.GenerativeCS.Options.OpenAI; 5 | using Microsoft.Extensions.DependencyInjection; 6 | 7 | namespace ChatAIze.GenerativeCS.Extensions; 8 | 9 | /// 10 | /// Dependency injection extensions for registering instances. 11 | /// 12 | public static class OpenAIExtension 13 | { 14 | /// 15 | /// Registers a typed OpenAI client using supplied options. 16 | /// 17 | /// Chat container type. 18 | /// Message type. 19 | /// Function call type. 20 | /// Function result type. 21 | /// Service collection to configure. 22 | /// Optional configuration callback. 23 | /// The same service collection. 24 | public static IServiceCollection AddOpenAIClient(this IServiceCollection services, Action>? options = null) 25 | where TChat : IChat, new() 26 | where TMessage : IChatMessage, new() 27 | where TFunctionCall : IFunctionCall, new() 28 | where TFunctionResult : IFunctionResult, new() 29 | { 30 | if (options is not null) 31 | { 32 | _ = services.Configure(options); 33 | 34 | if (options is Action options2) 35 | { 36 | _ = services.Configure(options2); 37 | } 38 | } 39 | 40 | _ = services.AddHttpClient>(c => c.Timeout = TimeSpan.FromMinutes(15)); 41 | _ = services.AddHttpClient(c => c.Timeout = TimeSpan.FromMinutes(15)); 42 | _ = services.AddSingleton>(); 43 | _ = services.AddSingleton(); 44 | 45 | return services; 46 | } 47 | 48 | /// 49 | /// Registers the default OpenAI client types using supplied options. 50 | /// 51 | /// Service collection to configure. 52 | /// Optional configuration callback. 53 | /// The same service collection. 54 | public static IServiceCollection AddOpenAIClient(this IServiceCollection services, Action>? options = null) 55 | { 56 | return services.AddOpenAIClient(options); 57 | } 58 | 59 | /// 60 | /// Registers a typed OpenAI client with an explicit API key. 61 | /// 62 | /// Chat container type. 63 | /// Message type. 64 | /// Function call type. 65 | /// Function result type. 66 | /// Service collection to configure. 67 | /// API key used for requests. 68 | /// The same service collection. 69 | public static IServiceCollection AddOpenAIClient(this IServiceCollection services, string apiKey) 70 | where TChat : IChat, new() 71 | where TMessage : IChatMessage, new() 72 | where TFunctionCall : IFunctionCall, new() 73 | where TFunctionResult : IFunctionResult, new() 74 | { 75 | return services.AddOpenAIClient(o => 76 | { 77 | o.ApiKey = apiKey; 78 | }); 79 | } 80 | 81 | /// 82 | /// Registers the default OpenAI client types with an explicit API key. 83 | /// 84 | /// Service collection to configure. 85 | /// API key used for requests. 86 | /// The same service collection. 87 | public static IServiceCollection AddOpenAIClient(this IServiceCollection services, string apiKey) 88 | { 89 | return services.AddOpenAIClient(apiKey); 90 | } 91 | 92 | /// 93 | /// Registers a typed OpenAI client with default completion options. 94 | /// 95 | /// Chat container type. 96 | /// Message type. 97 | /// Function call type. 98 | /// Function result type. 99 | /// Service collection to configure. 100 | /// API key used for requests. 101 | /// Default completion options. 102 | /// The same service collection. 103 | public static IServiceCollection AddOpenAIClient(this IServiceCollection services, string apiKey, ChatCompletionOptions? defaultCompletionOptions) 104 | where TChat : IChat, new() 105 | where TMessage : IChatMessage, new() 106 | where TFunctionCall : IFunctionCall, new() 107 | where TFunctionResult : IFunctionResult, new() 108 | { 109 | return services.AddOpenAIClient(o => 110 | { 111 | o.ApiKey = apiKey; 112 | 113 | if (defaultCompletionOptions is not null) 114 | { 115 | o.DefaultCompletionOptions = defaultCompletionOptions; 116 | } 117 | }); 118 | } 119 | 120 | /// 121 | /// Registers the default OpenAI client types with default completion options. 122 | /// 123 | /// Service collection to configure. 124 | /// API key used for requests. 125 | /// Default completion options. 126 | /// The same service collection. 127 | public static IServiceCollection AddOpenAIClient(this IServiceCollection services, string apiKey, ChatCompletionOptions? defaultCompletionOptions) 128 | { 129 | return services.AddOpenAIClient(apiKey, defaultCompletionOptions); 130 | } 131 | 132 | /// 133 | /// Registers a typed OpenAI client with default embedding options. 134 | /// 135 | /// Chat container type. 136 | /// Message type. 137 | /// Function call type. 138 | /// Function result type. 139 | /// Service collection to configure. 140 | /// API key used for requests. 141 | /// Default embedding options. 142 | /// The same service collection. 143 | public static IServiceCollection AddOpenAIClient(this IServiceCollection services, string apiKey, EmbeddingOptions? defaultEmbeddingOptions) 144 | where TChat : IChat, new() 145 | where TMessage : IChatMessage, new() 146 | where TFunctionCall : IFunctionCall, new() 147 | where TFunctionResult : IFunctionResult, new() 148 | { 149 | return services.AddOpenAIClient(o => 150 | { 151 | o.ApiKey = apiKey; 152 | 153 | if (defaultEmbeddingOptions is not null) 154 | { 155 | o.DefaultEmbeddingOptions = defaultEmbeddingOptions; 156 | } 157 | }); 158 | } 159 | 160 | /// 161 | /// Registers the default OpenAI client types with default embedding options. 162 | /// 163 | /// Service collection to configure. 164 | /// API key used for requests. 165 | /// Default embedding options. 166 | /// The same service collection. 167 | public static IServiceCollection AddOpenAIClient(this IServiceCollection services, string apiKey, EmbeddingOptions? defaultEmbeddingOptions) 168 | { 169 | return services.AddOpenAIClient(apiKey, defaultEmbeddingOptions); 170 | } 171 | 172 | /// 173 | /// Registers a typed OpenAI client with default text-to-speech options. 174 | /// 175 | /// Chat container type. 176 | /// Message type. 177 | /// Function call type. 178 | /// Function result type. 179 | /// Service collection to configure. 180 | /// API key used for requests. 181 | /// Default text-to-speech options. 182 | /// The same service collection. 183 | public static IServiceCollection AddOpenAIClient(this IServiceCollection services, string apiKey, TextToSpeechOptions? defaultTextToSpeechOptions) 184 | where TChat : IChat, new() 185 | where TMessage : IChatMessage, new() 186 | where TFunctionCall : IFunctionCall, new() 187 | where TFunctionResult : IFunctionResult, new() 188 | { 189 | return services.AddOpenAIClient(o => 190 | { 191 | o.ApiKey = apiKey; 192 | 193 | if (defaultTextToSpeechOptions is not null) 194 | { 195 | o.DefaultTextToSpeechOptions = defaultTextToSpeechOptions; 196 | } 197 | }); 198 | } 199 | 200 | /// 201 | /// Registers the default OpenAI client types with default text-to-speech options. 202 | /// 203 | /// Service collection to configure. 204 | /// API key used for requests. 205 | /// Default text-to-speech options. 206 | /// The same service collection. 207 | public static IServiceCollection AddOpenAIClient(this IServiceCollection services, string apiKey, TextToSpeechOptions? defaultTextToSpeechOptions) 208 | { 209 | return services.AddOpenAIClient(apiKey, defaultTextToSpeechOptions); 210 | } 211 | 212 | /// 213 | /// Registers a typed OpenAI client with default transcription options. 214 | /// 215 | /// Chat container type. 216 | /// Message type. 217 | /// Function call type. 218 | /// Function result type. 219 | /// Service collection to configure. 220 | /// API key used for requests. 221 | /// Default transcription options. 222 | /// The same service collection. 223 | public static IServiceCollection AddOpenAIClient(this IServiceCollection services, string apiKey, TranscriptionOptions? defaultTranscriptionOptions) 224 | where TChat : IChat, new() 225 | where TMessage : IChatMessage, new() 226 | where TFunctionCall : IFunctionCall, new() 227 | where TFunctionResult : IFunctionResult, new() 228 | { 229 | return services.AddOpenAIClient(o => 230 | { 231 | o.ApiKey = apiKey; 232 | 233 | if (defaultTranscriptionOptions is not null) 234 | { 235 | o.DefaultTranscriptionOptions = defaultTranscriptionOptions; 236 | } 237 | }); 238 | } 239 | 240 | /// 241 | /// Registers the default OpenAI client types with default transcription options. 242 | /// 243 | /// Service collection to configure. 244 | /// API key used for requests. 245 | /// Default transcription options. 246 | /// The same service collection. 247 | public static IServiceCollection AddOpenAIClient(this IServiceCollection services, string apiKey, TranscriptionOptions? defaultTranscriptionOptions) 248 | { 249 | return services.AddOpenAIClient(apiKey, defaultTranscriptionOptions); 250 | } 251 | 252 | /// 253 | /// Registers a typed OpenAI client with default translation options. 254 | /// 255 | /// Chat container type. 256 | /// Message type. 257 | /// Function call type. 258 | /// Function result type. 259 | /// Service collection to configure. 260 | /// API key used for requests. 261 | /// Default translation options. 262 | /// The same service collection. 263 | public static IServiceCollection AddOpenAIClient(this IServiceCollection services, string apiKey, TranslationOptions? defaultTranslationOptions) 264 | where TChat : IChat, new() 265 | where TMessage : IChatMessage, new() 266 | where TFunctionCall : IFunctionCall, new() 267 | where TFunctionResult : IFunctionResult, new() 268 | { 269 | return services.AddOpenAIClient(o => 270 | { 271 | o.ApiKey = apiKey; 272 | 273 | if (defaultTranslationOptions is not null) 274 | { 275 | o.DefaultTranslationOptions = defaultTranslationOptions; 276 | } 277 | }); 278 | } 279 | 280 | /// 281 | /// Registers the default OpenAI client types with default translation options. 282 | /// 283 | /// Service collection to configure. 284 | /// API key used for requests. 285 | /// Default translation options. 286 | /// The same service collection. 287 | public static IServiceCollection AddOpenAIClient(this IServiceCollection services, string apiKey, TranslationOptions? defaultTranslationOptions) 288 | { 289 | return services.AddOpenAIClient(apiKey, defaultTranslationOptions); 290 | } 291 | } 292 | -------------------------------------------------------------------------------- /ChatAIze.GenerativeCS/Providers/Gemini/ChatCompletion.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.Text.Encodings.Web; 3 | using System.Text.Json; 4 | using System.Text.Json.Nodes; 5 | using ChatAIze.Abstractions.Chat; 6 | using ChatAIze.GenerativeCS.Options.Gemini; 7 | using ChatAIze.GenerativeCS.Utilities; 8 | using ChatAIze.Utilities.Extensions; 9 | 10 | namespace ChatAIze.GenerativeCS.Providers.Gemini; 11 | 12 | /// 13 | /// Handles Gemini chat completion requests and function calling flows. 14 | /// 15 | public static class ChatCompletion 16 | { 17 | /// 18 | /// Serializer options used for chat and function payloads. 19 | /// 20 | private static JsonSerializerOptions JsonOptions { get; } = new() 21 | { 22 | Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, 23 | PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower 24 | }; 25 | 26 | /// 27 | /// Executes a text-only completion request using Gemini. 28 | /// 29 | /// Chat container type. 30 | /// Message type. 31 | /// Function call type. 32 | /// Function result type. 33 | /// User prompt text. 34 | /// API key used for the request. 35 | /// Optional completion options. 36 | /// HTTP client to use. 37 | /// Cancellation token for the operation. 38 | /// Generated response text. 39 | internal static async Task CompleteAsync(string prompt, string? apiKey, ChatCompletionOptions? options = null, HttpClient? httpClient = null, CancellationToken cancellationToken = default) 40 | where TChat : IChat, new() 41 | where TMessage : IChatMessage, new() 42 | where TFunctionCall : IFunctionCall, new() 43 | where TFunctionResult : IFunctionResult, new() 44 | { 45 | httpClient ??= new() 46 | { 47 | Timeout = TimeSpan.FromMinutes(15) 48 | }; 49 | 50 | options ??= new(); 51 | 52 | if (!string.IsNullOrWhiteSpace(options.ApiKey)) 53 | { 54 | apiKey = options.ApiKey; 55 | } 56 | 57 | if (options.Functions.Count >= 1) 58 | { 59 | var chat = new TChat(); 60 | _ = await chat.FromUserAsync(prompt); 61 | 62 | return await CompleteAsync(chat, apiKey, options, httpClient, cancellationToken); 63 | } 64 | 65 | var request = CreateCompletionRequest(prompt); 66 | if (options.IsDebugMode) 67 | { 68 | Console.WriteLine(request.ToString()); 69 | } 70 | 71 | using var response = await httpClient.RepeatPostAsJsonAsync($"https://generativelanguage.googleapis.com/v1beta/models/{options.Model}:generateContent?key={apiKey}", request, null, options.MaxAttempts, cancellationToken); 72 | using var responseContent = await response.Content.ReadAsStreamAsync(cancellationToken); 73 | using var responseDocument = await JsonDocument.ParseAsync(responseContent, cancellationToken: cancellationToken); 74 | 75 | if (options.IsDebugMode) 76 | { 77 | Console.WriteLine(responseDocument.RootElement.ToString()); 78 | } 79 | 80 | var generatedMessage = responseDocument.RootElement.GetProperty("candidates")[0]; 81 | var messageContent = generatedMessage.GetProperty("content").GetProperty("parts")[0].GetProperty("text").GetString()!; 82 | 83 | return messageContent; 84 | } 85 | 86 | /// 87 | /// Executes a chat completion request based on the provided transcript. 88 | /// 89 | /// Chat container type. 90 | /// Message type. 91 | /// Function call type. 92 | /// Function result type. 93 | /// Chat transcript to send. 94 | /// API key used for the request. 95 | /// Optional completion options. 96 | /// HTTP client to use. 97 | /// Cancellation token for the operation. 98 | /// Generated response text. 99 | internal static async Task CompleteAsync(TChat chat, string? apiKey, ChatCompletionOptions? options = null, HttpClient? httpClient = null, CancellationToken cancellationToken = default) 100 | where TChat : IChat 101 | where TMessage : IChatMessage, new() 102 | where TFunctionCall : IFunctionCall, new() 103 | where TFunctionResult : IFunctionResult, new() 104 | { 105 | httpClient ??= new() 106 | { 107 | Timeout = TimeSpan.FromMinutes(15) 108 | }; 109 | 110 | options ??= new(); 111 | 112 | if (!string.IsNullOrWhiteSpace(options.ApiKey)) 113 | { 114 | apiKey = options.ApiKey; 115 | } 116 | 117 | var request = CreateChatCompletionRequest(chat, options); 118 | if (options.IsDebugMode) 119 | { 120 | Console.WriteLine(request.ToString()); 121 | } 122 | 123 | using var response = await httpClient.RepeatPostAsJsonAsync($"https://generativelanguage.googleapis.com/v1beta/models/{options.Model}:generateContent?key={apiKey}", request, null, options.MaxAttempts, cancellationToken); 124 | using var responseContent = await response.Content.ReadAsStreamAsync(cancellationToken); 125 | using var responseDocument = await JsonDocument.ParseAsync(responseContent, cancellationToken: cancellationToken); 126 | 127 | if (options.IsDebugMode) 128 | { 129 | Console.WriteLine(responseDocument.RootElement.ToString()); 130 | } 131 | 132 | var responseParts = responseDocument.RootElement.GetProperty("candidates")[0].GetProperty("content").GetProperty("parts"); 133 | 134 | string messageContent = null!; 135 | foreach (var part in responseParts.EnumerateArray()) 136 | { 137 | if (part.TryGetProperty("functionCall", out var functionCallElement) && functionCallElement.TryGetProperty("name", out var functionNameElement)) 138 | { 139 | var functionName = functionNameElement.GetString()!; 140 | var functionArguments = functionCallElement.GetProperty("args").GetRawText()!; 141 | 142 | var message1 = await chat.FromChatbotAsync(new TFunctionCall { Name = functionName, Arguments = functionArguments }); 143 | await options.AddMessageCallback(message1); 144 | 145 | var function = options.Functions.FirstOrDefault(f => f.Name.NormalizedEquals(functionName)); 146 | if (function is not null) 147 | { 148 | // Alternate between asking for confirmation and executing for functions that demand a double check. 149 | if (function.RequiresDoubleCheck && chat.Messages.Count(m => m.FunctionCalls.Any(c => c.Name == functionName)) % 2 != 0) 150 | { 151 | var message2 = await chat.FromFunctionAsync(new TFunctionResult { Name = functionName, Value = "Before executing, are you sure the user wants to run this function? If yes, call it again to confirm." }); 152 | await options.AddMessageCallback(message2); 153 | } 154 | else 155 | { 156 | if (function.Callback is not null) 157 | { 158 | var functionValue = await function.Callback.InvokeForStringResultAsync(functionArguments, options.FunctionContext, cancellationToken); 159 | var message3 = await chat.FromFunctionAsync(new TFunctionResult { Name = functionName, Value = functionValue }); 160 | 161 | await options.AddMessageCallback(message3); 162 | } 163 | else 164 | { 165 | var functionValue = await options.DefaultFunctionCallback(functionName, functionArguments, cancellationToken); 166 | var message4 = await chat.FromFunctionAsync(new TFunctionResult { Name = functionName, Value = JsonSerializer.Serialize(functionValue, JsonOptions) }); 167 | 168 | await options.AddMessageCallback(message4); 169 | } 170 | } 171 | } 172 | else 173 | { 174 | var message5 = await chat.FromFunctionAsync(new TFunctionResult { Name = functionName, Value = $"Function '{functionName}' was not found." }); 175 | await options.AddMessageCallback(message5); 176 | } 177 | 178 | // Try again now that the function response has been added to the transcript. 179 | return await CompleteAsync(chat, apiKey, options, httpClient, cancellationToken); 180 | } 181 | else if (part.TryGetProperty("text", out var textElement)) 182 | { 183 | messageContent = textElement.GetString()!; 184 | 185 | var message6 = await chat.FromChatbotAsync(messageContent); 186 | await options.AddMessageCallback(message6); 187 | } 188 | else 189 | { 190 | var message7 = await chat.FromFunctionAsync(new TFunctionResult { Name = "Error", Value = "Either call a function or respond with text." }); 191 | await options.AddMessageCallback(message7); 192 | 193 | // Retry the completion with the synthetic tool result to coax the model into a valid response. 194 | return await CompleteAsync(chat, apiKey, options, httpClient, cancellationToken); 195 | } 196 | } 197 | 198 | return messageContent; 199 | } 200 | 201 | /// 202 | /// Builds the JSON payload for a simple completion request. 203 | /// 204 | /// User prompt text. 205 | /// JSON request payload. 206 | private static JsonObject CreateCompletionRequest(string prompt) 207 | { 208 | var partObject = new JsonObject 209 | { 210 | ["text"] = prompt 211 | }; 212 | 213 | var partsArray = new JsonArray 214 | { 215 | partObject 216 | }; 217 | 218 | var contentObject = new JsonObject 219 | { 220 | ["parts"] = partsArray 221 | }; 222 | 223 | var contentsArray = new JsonArray 224 | { 225 | contentObject 226 | }; 227 | 228 | var requestObject = new JsonObject 229 | { 230 | ["contents"] = contentsArray 231 | }; 232 | 233 | return requestObject; 234 | } 235 | 236 | /// 237 | /// Builds the JSON payload for a chat completion request. 238 | /// 239 | /// Chat container type. 240 | /// Message type. 241 | /// Function call type. 242 | /// Function result type. 243 | /// Chat transcript to send. 244 | /// Completion options. 245 | /// JSON request payload. 246 | private static JsonObject CreateChatCompletionRequest(TChat chat, ChatCompletionOptions options) 247 | where TChat : IChat 248 | where TMessage : IChatMessage, new() 249 | where TFunctionCall : IFunctionCall 250 | where TFunctionResult : IFunctionResult 251 | { 252 | var messages = chat.Messages.ToList(); 253 | 254 | if (options.SystemMessageCallback is not null) 255 | { 256 | MessageTools.AddDynamicSystemMessage(messages, options.SystemMessageCallback()); 257 | } 258 | 259 | if (options.IsTimeAware) 260 | { 261 | MessageTools.AddTimeInformation(messages, options.TimeCallback()); 262 | } 263 | 264 | MessageTools.RemoveDeletedMessages(messages); 265 | MessageTools.LimitTokens(messages, options.MessageLimit, options.CharacterLimit); 266 | // Gemini does not support a system/developer role; fold those into the user role instead. 267 | MessageTools.ReplaceSystemRole(messages); 268 | // Gemini expects contiguous text blocks; collapse sequential messages from the same speaker. 269 | MessageTools.MergeMessages(messages); 270 | 271 | var contentsArray = new JsonArray(); 272 | foreach (var message in messages) 273 | { 274 | var partObject = new JsonObject(); 275 | var functionCall = message.FunctionCalls.FirstOrDefault(); 276 | 277 | if (functionCall is not null) 278 | { 279 | var functionCallObject = new JsonObject 280 | { 281 | ["name"] = functionCall.Name 282 | }; 283 | 284 | if (functionCall.Arguments is not null) 285 | { 286 | // Gemini expects function arguments as structured JSON, not the raw string provided by OpenAI-style payloads. 287 | functionCallObject["args"] = JsonNode.Parse(functionCall.Arguments)!.AsObject(); 288 | } 289 | 290 | partObject["functionCall"] = functionCallObject; 291 | } 292 | else if (message.FunctionResult is not null && !string.IsNullOrWhiteSpace(message.FunctionResult.Name)) 293 | { 294 | var responseObject = new JsonObject 295 | { 296 | ["name"] = message.FunctionResult.Name, 297 | ["content"] = JsonSerializer.SerializeToNode(message.FunctionResult.Value, JsonOptions) 298 | }; 299 | 300 | var functionResponseObject = new JsonObject 301 | { 302 | ["name"] = message.FunctionResult.Name, 303 | ["response"] = responseObject 304 | }; 305 | 306 | partObject["functionResponse"] = functionResponseObject; 307 | } 308 | else 309 | { 310 | partObject["text"] = message.Content; 311 | } 312 | 313 | var partsArray = new JsonArray 314 | { 315 | partObject 316 | }; 317 | 318 | var contentObject = new JsonObject 319 | { 320 | ["role"] = GetRoleName(message.Role), 321 | ["parts"] = partsArray 322 | }; 323 | 324 | contentsArray.Add(contentObject); 325 | } 326 | 327 | var requestObject = new JsonObject 328 | { 329 | ["contents"] = contentsArray 330 | }; 331 | 332 | if (options.Functions.Count > 0) 333 | { 334 | var functionsArray = new JsonArray(); 335 | foreach (var function in options.Functions) 336 | { 337 | functionsArray.Add(SchemaSerializer.SerializeFunction(function, useOpenAIFeatures: false, isStrictModeOn: false)); 338 | } 339 | 340 | var functionsObject = new JsonObject 341 | { 342 | ["function_declarations"] = functionsArray 343 | }; 344 | 345 | var toolsArray = new JsonArray 346 | { 347 | functionsObject 348 | }; 349 | 350 | requestObject["tools"] = toolsArray; 351 | } 352 | 353 | return requestObject; 354 | } 355 | 356 | /// 357 | /// Converts a chat role into the provider-specific role name. 358 | /// 359 | /// Role to convert. 360 | /// Provider-specific role string. 361 | private static string GetRoleName(ChatRole role) 362 | { 363 | return role switch 364 | { 365 | ChatRole.System => "user", 366 | ChatRole.User => "user", 367 | ChatRole.Chatbot => "model", 368 | ChatRole.Function => "tool", 369 | _ => throw new ArgumentOutOfRangeException(nameof(role), role, "Invalid role") 370 | }; 371 | } 372 | } 373 | -------------------------------------------------------------------------------- /ChatAIze.GenerativeCS/Clients/GeminiClient.cs: -------------------------------------------------------------------------------- 1 | using ChatAIze.Abstractions.Chat; 2 | using ChatAIze.GenerativeCS.Models; 3 | using ChatAIze.GenerativeCS.Options.Gemini; 4 | using ChatAIze.GenerativeCS.Providers.Gemini; 5 | using ChatAIze.GenerativeCS.Utilities; 6 | using Microsoft.Extensions.DependencyInjection; 7 | using Microsoft.Extensions.Options; 8 | 9 | namespace ChatAIze.GenerativeCS.Clients; 10 | 11 | /// 12 | /// Client wrapper around the Gemini API for chat completions with optional function calling support. 13 | /// 14 | /// Concrete chat container type. 15 | /// Concrete chat message type. 16 | /// Concrete function call type. 17 | /// Concrete function result type. 18 | public class GeminiClient 19 | where TChat : IChat, new() 20 | where TMessage : IChatMessage, new() 21 | where TFunctionCall : IFunctionCall, new() 22 | where TFunctionResult : IFunctionResult, new() 23 | { 24 | private readonly HttpClient _httpClient = new() 25 | { 26 | // Long timeout to accommodate streaming and large uploads. 27 | Timeout = TimeSpan.FromMinutes(15) 28 | }; 29 | 30 | /// 31 | /// Initializes a new instance of the client, reading the API key from the environment when available. 32 | /// 33 | public GeminiClient() 34 | { 35 | // Allow environment variables to supply credentials when no explicit API key is provided. 36 | if (string.IsNullOrWhiteSpace(ApiKey)) 37 | { 38 | ApiKey = EnvironmentVariableManager.GetGeminiAPIKey(); 39 | } 40 | } 41 | 42 | /// 43 | /// Initializes a new instance of the client with an explicit API key or falls back to the environment. 44 | /// 45 | /// Gemini API key. 46 | public GeminiClient(string apiKey) 47 | { 48 | ApiKey = apiKey; 49 | 50 | if (string.IsNullOrWhiteSpace(ApiKey)) 51 | { 52 | ApiKey = EnvironmentVariableManager.GetGeminiAPIKey(); 53 | } 54 | } 55 | 56 | /// 57 | /// Initializes a new instance of the client with explicit default options. 58 | /// 59 | /// Typed client options. 60 | public GeminiClient(GeminiClientOptions options) 61 | { 62 | ApiKey = options.ApiKey; 63 | 64 | if (string.IsNullOrWhiteSpace(ApiKey)) 65 | { 66 | ApiKey = EnvironmentVariableManager.GetGeminiAPIKey(); 67 | } 68 | 69 | DefaultCompletionOptions = options.DefaultCompletionOptions; 70 | } 71 | 72 | /// 73 | /// Initializes a new instance of the client using dependency injection managed options. 74 | /// 75 | /// Preconfigured HTTP client. 76 | /// Typed options snapshot from DI. 77 | [ActivatorUtilitiesConstructor] 78 | public GeminiClient(HttpClient httpClient, IOptions> options) 79 | { 80 | _httpClient = httpClient; 81 | ApiKey = options.Value.ApiKey; 82 | 83 | if (string.IsNullOrWhiteSpace(ApiKey)) 84 | { 85 | ApiKey = EnvironmentVariableManager.GetGeminiAPIKey(); 86 | } 87 | 88 | DefaultCompletionOptions = options.Value.DefaultCompletionOptions; 89 | } 90 | 91 | /// 92 | /// Initializes a new instance of the client with default completion options. 93 | /// 94 | /// Default chat completion options. 95 | public GeminiClient(ChatCompletionOptions defaultCompletionOptions) 96 | { 97 | DefaultCompletionOptions = defaultCompletionOptions; 98 | } 99 | 100 | /// 101 | /// Gets or sets the API key used for outbound Gemini requests. 102 | /// 103 | public string? ApiKey { get; set; } 104 | 105 | /// 106 | /// Gets or sets the default chat completion options applied when none are supplied. 107 | /// 108 | public ChatCompletionOptions DefaultCompletionOptions { get; set; } = new(); 109 | 110 | /// 111 | /// Runs a one-off text completion for the supplied prompt. 112 | /// 113 | /// User text to send to Gemini. 114 | /// Optional completion options. 115 | /// Cancellation token for the operation. 116 | /// Generated text from the model. 117 | public async Task CompleteAsync(string prompt, ChatCompletionOptions? options = null, CancellationToken cancellationToken = default) 118 | { 119 | return await ChatCompletion.CompleteAsync(prompt, ApiKey, options ?? DefaultCompletionOptions, _httpClient, cancellationToken); 120 | } 121 | 122 | /// 123 | /// Runs a completion using the provided chat transcript. 124 | /// 125 | /// Chat history containing prior messages. 126 | /// Optional completion options. 127 | /// Cancellation token for the operation. 128 | /// Generated text from the model. 129 | public async Task CompleteAsync(TChat chat, ChatCompletionOptions? options = null, CancellationToken cancellationToken = default) 130 | { 131 | return await ChatCompletion.CompleteAsync(chat, ApiKey, options ?? DefaultCompletionOptions, _httpClient, cancellationToken); 132 | } 133 | 134 | /// 135 | /// Registers a prebuilt function for inclusion in completions. 136 | /// 137 | /// Function metadata to register. 138 | public void AddFunction(IChatFunction function) 139 | { 140 | DefaultCompletionOptions.Functions.Add(function); 141 | } 142 | 143 | /// 144 | /// Registers a function by name without a description. 145 | /// 146 | /// Function name. 147 | /// True to require a confirmation loop before execution. 148 | public void AddFunction(string name, bool requiresDoubleCheck = false) 149 | { 150 | DefaultCompletionOptions.Functions.Add(new ChatFunction(name, requiresDoubleCheck)); 151 | } 152 | 153 | /// 154 | /// Registers a function by name with a description. 155 | /// 156 | /// Function name. 157 | /// Function description. 158 | /// True to require a confirmation loop before execution. 159 | public void AddFunction(string name, string? description, bool requiresDoubleCheck = false) 160 | { 161 | DefaultCompletionOptions.Functions.Add(new ChatFunction(name, description, requiresDoubleCheck)); 162 | } 163 | 164 | /// 165 | /// Registers a function by delegate callback. 166 | /// 167 | /// Delegate implementing the function. 168 | public void AddFunction(Delegate callback) 169 | { 170 | DefaultCompletionOptions.Functions.Add(new ChatFunction(callback)); 171 | } 172 | 173 | /// 174 | /// Registers a named function by delegate callback. 175 | /// 176 | /// Function name. 177 | /// Delegate implementing the function. 178 | public void AddFunction(string name, Delegate callback) 179 | { 180 | DefaultCompletionOptions.Functions.Add(new ChatFunction(name, callback)); 181 | } 182 | 183 | /// 184 | /// Registers a named function with explicit parameters. 185 | /// 186 | /// Function name. 187 | /// Function parameters. 188 | public void AddFunction(string name, ICollection parameters) 189 | { 190 | DefaultCompletionOptions.Functions.Add(new ChatFunction(name, parameters)); 191 | } 192 | 193 | /// 194 | /// Registers a named function with explicit parameters. 195 | /// 196 | /// Function name. 197 | /// Function parameters. 198 | public void AddFunction(string name, params IFunctionParameter[] parameters) 199 | { 200 | DefaultCompletionOptions.Functions.Add(new ChatFunction(name, parameters)); 201 | } 202 | 203 | /// 204 | /// Registers a named function with a description and delegate callback. 205 | /// 206 | /// Function name. 207 | /// Function description. 208 | /// Delegate implementing the function. 209 | public void AddFunction(string name, string? description, Delegate callback) 210 | { 211 | DefaultCompletionOptions.Functions.Add(new ChatFunction(name, description, callback)); 212 | } 213 | 214 | /// 215 | /// Registers a named function with a description and explicit parameters. 216 | /// 217 | /// Function name. 218 | /// Function description. 219 | /// Function parameters. 220 | public void AddFunction(string name, string? description, ICollection parameters) 221 | { 222 | DefaultCompletionOptions.Functions.Add(new ChatFunction(name, description, parameters)); 223 | } 224 | 225 | /// 226 | /// Registers a named function with a description and explicit parameters. 227 | /// 228 | /// Function name. 229 | /// Function description. 230 | /// Function parameters. 231 | public void AddFunction(string name, string? description, params IFunctionParameter[] parameters) 232 | { 233 | DefaultCompletionOptions.Functions.Add(new ChatFunction(name, description, parameters)); 234 | } 235 | 236 | /// 237 | /// Registers a named function that requires double checking and maps to a delegate callback. 238 | /// 239 | /// Function name. 240 | /// True to require a confirmation loop before execution. 241 | /// Delegate implementing the function. 242 | public void AddFunction(string name, bool requiresDoubleCheck, Delegate callback) 243 | { 244 | DefaultCompletionOptions.Functions.Add(new ChatFunction(name, requiresDoubleCheck, callback)); 245 | } 246 | 247 | /// 248 | /// Registers a named function that requires double checking and declares parameters. 249 | /// 250 | /// Function name. 251 | /// True to require a confirmation loop before execution. 252 | /// Function parameters. 253 | public void AddFunction(string name, bool requiresDoubleCheck, ICollection parameters) 254 | { 255 | DefaultCompletionOptions.Functions.Add(new ChatFunction(name, requiresDoubleCheck, parameters)); 256 | } 257 | 258 | /// 259 | /// Registers a named function that requires double checking and declares parameters. 260 | /// 261 | /// Function name. 262 | /// True to require a confirmation loop before execution. 263 | /// Function parameters. 264 | public void AddFunction(string name, bool requiresDoubleCheck, params IFunctionParameter[] parameters) 265 | { 266 | DefaultCompletionOptions.Functions.Add(new ChatFunction(name, requiresDoubleCheck, parameters)); 267 | } 268 | 269 | /// 270 | /// Registers a named function with a description, confirmation loop, and delegate callback. 271 | /// 272 | /// Function name. 273 | /// Function description. 274 | /// True to require a confirmation loop before execution. 275 | /// Delegate implementing the function. 276 | public void AddFunction(string name, string? description, bool requiresDoubleCheck, Delegate callback) 277 | { 278 | DefaultCompletionOptions.Functions.Add(new ChatFunction(name, description, requiresDoubleCheck, callback)); 279 | } 280 | 281 | /// 282 | /// Registers a named function with a description, confirmation loop, and explicit parameters. 283 | /// 284 | /// Function name. 285 | /// Function description. 286 | /// True to require a confirmation loop before execution. 287 | /// Function parameters. 288 | public void AddFunction(string name, string? description, bool requiresDoubleCheck, ICollection parameters) 289 | { 290 | DefaultCompletionOptions.Functions.Add(new ChatFunction(name, description, requiresDoubleCheck, parameters)); 291 | } 292 | 293 | /// 294 | /// Registers a named function with a description, confirmation loop, and explicit parameters. 295 | /// 296 | /// Function name. 297 | /// Function description. 298 | /// True to require a confirmation loop before execution. 299 | /// Function parameters. 300 | public void AddFunction(string name, string? description, bool requiresDoubleCheck, params IFunctionParameter[] parameters) 301 | { 302 | DefaultCompletionOptions.Functions.Add(new ChatFunction(name, description, requiresDoubleCheck, parameters)); 303 | } 304 | 305 | /// 306 | /// Removes a registered function by reference. 307 | /// 308 | /// Function metadata instance to remove. 309 | /// True when the function was removed. 310 | public bool RemoveFunction(ChatFunction function) 311 | { 312 | return DefaultCompletionOptions.Functions.Remove(function); 313 | } 314 | 315 | /// 316 | /// Removes a registered function by name. 317 | /// 318 | /// Function name to remove. 319 | /// True when the function was removed. 320 | public bool RemoveFunction(string name) 321 | { 322 | var function = DefaultCompletionOptions.Functions.FirstOrDefault(f => f.Name == name); 323 | if (function is null) 324 | { 325 | return false; 326 | } 327 | 328 | return DefaultCompletionOptions.Functions.Remove(function); 329 | } 330 | 331 | /// 332 | /// Removes a registered function that matches the supplied callback. 333 | /// 334 | /// Delegate used to locate the function. 335 | /// True when the function was removed. 336 | public bool RemoveFunction(Delegate callback) 337 | { 338 | var function = DefaultCompletionOptions.Functions.FirstOrDefault(f => f.Callback == callback); 339 | if (function is null) 340 | { 341 | return false; 342 | } 343 | 344 | return DefaultCompletionOptions.Functions.Remove(function); 345 | } 346 | 347 | /// 348 | /// Clears all registered functions from the default completion options. 349 | /// 350 | public void ClearFunctions() 351 | { 352 | DefaultCompletionOptions.Functions.Clear(); 353 | } 354 | } 355 | 356 | /// 357 | /// Non-generic convenience client using built-in chat, message, function call, and function result types. 358 | /// 359 | public class GeminiClient : GeminiClient 360 | { 361 | /// 362 | /// Initializes a new instance using environment configuration. 363 | /// 364 | public GeminiClient() : base() { } 365 | 366 | /// 367 | /// Initializes a new instance with an explicit API key. 368 | /// 369 | /// Gemini API key. 370 | public GeminiClient(string apiKey) : base(apiKey) { } 371 | 372 | /// 373 | /// Initializes a new instance with explicit default options. 374 | /// 375 | /// Gemini client options. 376 | public GeminiClient(GeminiClientOptions options) : base(options) { } 377 | 378 | /// 379 | /// Initializes a new instance using dependency injection managed options. 380 | /// 381 | /// Preconfigured HTTP client. 382 | /// Typed options snapshot from DI. 383 | [ActivatorUtilitiesConstructor] 384 | public GeminiClient(HttpClient httpClient, IOptions options) : base(httpClient, options) { } 385 | 386 | /// 387 | /// Initializes a new instance with a default chat completion configuration. 388 | /// 389 | /// Default chat completion options. 390 | public GeminiClient(ChatCompletionOptions defaultCompletionOptions) : base(defaultCompletionOptions) { } 391 | } 392 | --------------------------------------------------------------------------------