├── 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 |
--------------------------------------------------------------------------------