GPAL - Generally Positive Automation Library v1.0
GPAL The Fluent Automation LIbrary
Loading...
Searching...
No Matches
AIProvider.cs
1// =============================================================================
2// GPAL - Generally Positive Automation Library
3// Copyright © 2026 Software Decisions, Inc. All rights reserved.
4//
5// This file is part of GPAL.
6// Licensed under the Business Source License 1.1
7//
8// Primary development, architecture, and vision by Michael B. Vederman,
9// CEO of Software Decisions, Inc., Texas.
10//
11// Internal development maintained privately.
12// Public releases appear on GitHub: https://github.com/SoftwareDecisionsInc/GPAL.
13//
14// See LICENSE for full terms, including Additional Use Grant.
15// =============================================================================
16
17using System;
18using System.Collections.Generic;
19using System.Text.Json;
20using static GenerallyPositive.Enums;
21
22namespace GenerallyPositive
23{
24 public class AIProvider
25 {
26 private readonly AIProviderType _providerType;
27 private readonly IRESTClient _restClient;
28 private readonly ICredentials _credentials;
29 private readonly ClassificationConfig _config;
30 private readonly AIProviderDefinition _definition;
31
32 public AIProvider(AIProviderType providerType, IRESTClient restClient, ICredentials credentials, ClassificationConfig config)
33 {
34 _providerType = providerType;
35 _restClient = restClient ?? throw new ArgumentNullException(nameof(restClient));
36 _credentials = credentials;
37 _config = config ?? new ClassificationConfig();
39 _definition = AIProvidersConfig.Current.Find(providerType);
40 if (_definition == null)
41 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $"No provider definition found for [{providerType}]. Add it to AIProvidersConfig.yaml.", null, GPALObjectType.AIProvider);
42 }
43
44 public string Call(string prompt)
45 {
46 try
47 {
48 if (string.IsNullOrEmpty(prompt))
49 {
50 GPAL.PublishSimpleEvent(GPALEventType.ERROR, "Prompt cannot be empty.", this, GPALObjectType.AIProvider);
51 return string.Empty;
52 }
53
54 string endpoint = GetEndpoint();
55 string apiBase = GetApiBase();
56
57 string accessToken = null;
58 if (_credentials != null)
59 {
60 _credentials.FetchAccessToken(out accessToken);
61 if (string.IsNullOrEmpty(accessToken))
62 GPAL.PublishSimpleEvent(GPALEventType.ERROR, "Unable to login using credentials. No access token granted.", this, GPALObjectType.AIProvider);
63 }
64
65 var body = BuildRequestBody(prompt);
66
67 // NOTE: WithEndpoint() resets the client's headers, so headers must be added
68 // after WithAPIBase/WithEndpoint, not before.
69 var requestBuilder = (IAllowRESTParametersOrExecution)_restClient
70 .WithAPIBase(apiBase, System.Threading.Timeout.InfiniteTimeSpan)
71 .WithEndpoint(endpoint);
72
73 if (!string.IsNullOrEmpty(accessToken))
74 {
75 var headerName = _definition?.AuthHeader ?? "Authorization";
76 var prefix = _definition?.AuthPrefix ?? "Bearer ";
77 _restClient.WithHeader(headerName, string.IsNullOrEmpty(prefix) ? accessToken : $"{prefix}{accessToken}");
78 }
79
80 if (_definition?.AdditionalHeaders != null)
81 {
82 foreach (var h in _definition.AdditionalHeaders)
83 _restClient.WithHeader(h.Key, h.Value);
84 }
85
86 string response = requestBuilder
87 .WithParameters(body)
88 .Execute();
89
90 if (string.IsNullOrEmpty(response))
91 {
92 GPAL.PublishSimpleEvent(GPALEventType.ERROR, "Empty response from AI provider.", this, GPALObjectType.AIProvider);
93 return string.Empty;
94 }
95
96 string completion = ExtractCompletion(response);
97 if (string.IsNullOrEmpty(completion))
98 {
99 GPAL.PublishSimpleEvent(GPALEventType.ERROR, $"Unable to extract completion from [{_providerType}] response: [{response}]", this, GPALObjectType.AIProvider);
100 return string.Empty;
101 }
102
103 GPAL.PublishSimpleEvent(GPALEventType.INFO, $"Received response from [{_providerType}]", this, GPALObjectType.AIProvider);
104 return completion;
105 }
106 catch (Exception ex)
107 {
108 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, $"Failed to call AI provider", this, GPALObjectType.AIProvider, ex);
109 return string.Empty;
110 }
111 }
112
113 private Dictionary<string, object> BuildRequestBody(string prompt)
114 {
115 var messages = new List<object> { new Dictionary<string, object> { ["role"] = "user", ["content"] = prompt } };
116
117 var body = new Dictionary<string, object>
118 {
119 ["model"] = GetModel(),
120 ["messages"] = messages
121 };
122
123 if (_definition?.ResponseFormat == "anthropic")
124 {
125 body["max_tokens"] = GetMaxTokens();
126 }
127
128 if (_config.AdditionalParameters != null)
129 {
130 foreach (var param in _config.AdditionalParameters)
131 {
132 if (IsReservedParameter(param.Key))
133 continue;
134 body[param.Key] = param.Value;
135 }
136 }
137
138 return body;
139 }
140
141 private static bool IsReservedParameter(string key)
142 {
143 return key == "Endpoint" || key == "Model" || key == "MaxTokens" || key == "AnthropicVersion";
144 }
145
146 private int GetMaxTokens()
147 {
148 if (_config?.AdditionalParameters?.ContainsKey("MaxTokens") == true
149 && int.TryParse(_config.AdditionalParameters["MaxTokens"].ToString(), out int maxTokens))
150 {
151 return maxTokens;
152 }
153 return 1024;
154 }
155
156 private string GetModel()
157 {
158 if (_config?.AdditionalParameters?.ContainsKey("Model") == true)
159 return _config.AdditionalParameters["Model"].ToString();
160 return _definition?.DefaultModel;
161 }
162
163 private string GetApiBase() => _definition?.BaseUrl;
164
165 private string GetEndpoint()
166 {
167 if (_config?.AdditionalParameters?.ContainsKey("Endpoint") == true)
168 return _config.AdditionalParameters["Endpoint"].ToString();
169 return _definition?.ChatEndpoint ?? "/chat/completions";
170 }
171
172 private string ExtractCompletion(string response)
173 {
174 using var doc = JsonDocument.Parse(response);
175 var root = doc.RootElement;
176
177 if (_definition?.ResponseFormat == "anthropic")
178 {
179 if (root.TryGetProperty("content", out var content) && content.ValueKind == JsonValueKind.Array && content.GetArrayLength() > 0)
180 {
181 var first = content[0];
182 if (first.TryGetProperty("text", out var text))
183 return text.GetString();
184 }
185 return null;
186 }
187
188 if (root.TryGetProperty("choices", out var choices) && choices.ValueKind == JsonValueKind.Array && choices.GetArrayLength() > 0)
189 {
190 var first = choices[0];
191 if (first.TryGetProperty("message", out var message) && message.TryGetProperty("content", out var msgContent))
192 return msgContent.GetString();
193 }
194 return null;
195 }
196 }
197}
Per-provider connection definition: base URL, endpoint, default model, auth style,...
Loads and saves the AI provider definitions used by GPAL.AI.WithProvider. Stored as ....
static AIProvidersConfig Load(GPALFile file=null)
Loads AI provider definitions from AIProvidersConfig.yaml (or a custom path). If the file is missing,...
Everything starts here, all the GPAL controls and global settings using fluent syntax are here....
Definition GPAL.cs:49
static void PublishSimpleEvent(GPALEventType gPALEventType, string msg, dynamic gPALObject=null, Enums.GPALObjectType gPALObjectType=GPALObjectType.None, Exception ex=null)
Publish a message to either the information channel or exception channel (if exception passed in) Pub...
Definition GPAL.cs:2406