Skip to content

Commit fcf1ad4

Browse files
author
Jefferson Pires
committed
Updated FunctionRequest and ApiAgent classes to include a new property for sending API responses to AI, and modified related methods and UI elements accordingly.
1 parent ae82080 commit fcf1ad4

File tree

12 files changed

+200
-53
lines changed

12 files changed

+200
-53
lines changed

OpenAI_API/Functions/FunctionRequest.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ public class Function
4949
/// Setting strict to true will ensure function calls reliably adhere to the function schema, instead of being best effort. We recommend always enabling strict mode.
5050
/// </summary>
5151
[JsonProperty("strict")]
52-
public bool Strict => true;
52+
public bool Strict => false;
5353
}
5454

5555
/// <summary>

README.md

+5
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,7 @@ Follow these steps to make the most of this feature:
198198
- **Identification**: Enter a unique name to identify the API.
199199
- **Base URL**: Enter the base URL of the API.
200200
- **Key/Values**: Define key-value pairs to be included in API requests, or to replace the key/values defined by the AI. Ideal for inserting authentication/authorization key/values, or to ensure that all calls have a certain key/value.
201+
- **Send Responses to AI**: If checked, all API responses will be forwarded to the AI so it can process and retain them in its context. Otherwise, the AI will only receive the HTTP status, and the responses will be displayed directly in the chat. This option is ideal if you want to protect data and save tokens.
201202
- **Definition**: Enter the API's definition (e.g., OpenAPI, Swagger, SOAP) here. This allows the AI to understand the API's structure and capabilities for making requests.
202203

203204
2. **Access the 'API' icon in Turbo Chat**
@@ -215,6 +216,10 @@ Follow these steps to make the most of this feature:
215216

216217
![image]()
217218

219+
Or if you prefer that the AI only receives the status code from the APIs, without the actual responses (after disabled the "Send Responses to AI" parameter):
220+
221+
![image]()
222+
218223
5. **Important considerations**
219224
- The AI ​​never has knowledge of the keys/values ​​configured through the options, so they are ideal for authentication tokens.
220225
- The AI ​​never has knowledge of the Base URL.

Resources/api.png

685 Bytes
Loading

VisualChatGPTStudio.csproj

+1
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,7 @@
226226
<Resource Include="Resources\diffView.png" />
227227
<Resource Include="Resources\DB.png" />
228228
<Resource Include="Resources\cloud.png" />
229+
<Resource Include="Resources\api.png" />
229230
<Content Include="Resources\Icon.png">
230231
<IncludeInVSIX>true</IncludeInVSIX>
231232
</Content>

VisualChatGPTStudio2019/VisualChatGPTStudio2019.csproj

+3
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,9 @@
181181
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
182182
<IncludeInVSIX>true</IncludeInVSIX>
183183
</Content>
184+
<Resource Include="..\Resources\api.png">
185+
<Link>Resources\api.png</Link>
186+
</Resource>
184187
<Content Include="..\Resources\cancelCommand.png">
185188
<Link>Resources\cancelCommand.png</Link>
186189
</Content>

VisualChatGPTStudioShared/Agents/ApiAgent/ApiAgent.cs

+125-42
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,17 @@
22
using OpenAI_API.Functions;
33
using System;
44
using System.Collections.Generic;
5+
using System.IO;
56
using System.Linq;
7+
using System.Net;
68
using System.Net.Http;
79
using System.Text;
810
using System.Threading.Tasks;
11+
using System.Xml;
12+
using System.Xml.Linq;
913
using VisualChatGPTStudioShared.Utils.Http;
1014
using VisualChatGPTStudioShared.Utils.Repositories;
15+
using Parameter = OpenAI_API.Functions.Parameter;
1116

1217
namespace VisualChatGPTStudioShared.Agents.ApiAgent
1318
{
@@ -92,12 +97,12 @@ public static List<FunctionRequest> GetApiFunctions()
9297
}
9398

9499
/// <summary>
95-
/// Executes the function based on the arguments provided by the AI.
100+
/// Executes an asynchronous function call to an API, handling both SOAP and REST requests based on the provided function parameters.
96101
/// </summary>
97-
/// <param name="function">The <see cref="FunctionResult"/> object with the function arguments.</param>
98-
/// <param name="logRequestAndResponse">Indicate whether the request and response should be logged.</param>
99-
/// <returns>The response returned by the API or exception details in case of an error.</returns>
100-
public static async Task<string> ExecuteFunctionAsync(FunctionResult function, bool logRequestAndResponse)
102+
/// <param name="function">The function result containing the API call details.</param>
103+
/// <param name="logRequestAndResponse">A boolean indicating whether to log the request and response.</param>
104+
/// <returns>A tuple where the first value refers to the response to be sent to the AI, and the second value refers to the API response to be displayed in the chat, when applicable.</returns>
105+
public static async Task<(string, string)> ExecuteFunctionAsync(FunctionResult function, bool logRequestAndResponse)
101106
{
102107
try
103108
{
@@ -109,7 +114,7 @@ public static async Task<string> ExecuteFunctionAsync(FunctionResult function, b
109114

110115
if (apiDefinition == null)
111116
{
112-
return $"API with name {apiName} was not found.";
117+
return ($"API with name {apiName} was not found.", null);
113118
}
114119

115120
string endPoint = arguments[nameof(endPoint)]?.Value<string>();
@@ -129,41 +134,59 @@ public static async Task<string> ExecuteFunctionAsync(FunctionResult function, b
129134
}
130135
}
131136

137+
HttpStatusCode responseStatusCode;
138+
string responseContent;
139+
132140
if (function.Function.Name == nameof(CallSoapApiAsync))
133141
{
134142
string soapAction = arguments["soapAction"]?.Value<string>();
135143
string soapEnvelope = arguments["soapEnvelope"]?.Value<string>();
136144

137-
return await CallSoapApiAsync(apiDefinition, endPoint, soapAction, headers, soapEnvelope, logRequestAndResponse);
138-
}
145+
HttpResponseMessage response = await CallSoapApiAsync(apiDefinition, endPoint, soapAction, headers, soapEnvelope, logRequestAndResponse);
139146

140-
string method = arguments[nameof(method)]?.Value<string>();
147+
responseStatusCode = response.StatusCode;
148+
responseContent = FormatXml(await response.Content.ReadAsStringAsync());
149+
}
150+
else
151+
{
152+
string method = arguments[nameof(method)]?.Value<string>();
141153

142-
// Optional query parameters
143-
Dictionary<string, string> queryParams = arguments[nameof(queryParams)]?.ToObject<Dictionary<string, string>>() ?? [];
154+
// Optional query parameters
155+
Dictionary<string, string> queryParams = arguments[nameof(queryParams)]?.ToObject<Dictionary<string, string>>() ?? [];
144156

145-
// Request body (for POST, PUT, PATCH, etc.)
146-
string body = arguments[nameof(body)]?.Value<string>();
157+
// Request body (for POST, PUT, PATCH, etc.)
158+
string body = arguments[nameof(body)]?.Value<string>();
147159

148-
foreach (ApiTagItem tag in apiDefinition.Tags.Where(t => t.Type == ApiTagType.QueryString))
149-
{
150-
if (queryParams.ContainsKey(tag.Key))
160+
foreach (ApiTagItem tag in apiDefinition.Tags.Where(t => t.Type == ApiTagType.QueryString))
151161
{
152-
queryParams[tag.Key] = tag.Value;
153-
}
154-
else
155-
{
156-
queryParams.Add(tag.Key, tag.Value);
162+
if (queryParams.ContainsKey(tag.Key))
163+
{
164+
queryParams[tag.Key] = tag.Value;
165+
}
166+
else
167+
{
168+
queryParams.Add(tag.Key, tag.Value);
169+
}
157170
}
171+
172+
HttpResponseMessage response = await CallRestApiAsync(apiDefinition, endPoint, method, headers, queryParams, body, logRequestAndResponse);
173+
174+
responseStatusCode = response.StatusCode;
175+
responseContent = FormatJson(await response.Content.ReadAsStringAsync());
176+
}
177+
178+
if (apiDefinition.SendResponsesToAI)
179+
{
180+
return ($"Response Status Code: {responseStatusCode}{Environment.NewLine}{responseContent}", null);
158181
}
159182

160-
return await CallRestApiAsync(apiDefinition, endPoint, method, headers, queryParams, body, logRequestAndResponse);
183+
return ($"Response Status Code: {responseStatusCode}", responseContent);
161184
}
162185
catch (Exception ex)
163186
{
164187
Logger.Log(ex);
165188

166-
return ex.Message;
189+
return (ex.Message, null);
167190
}
168191
}
169192

@@ -180,17 +203,17 @@ public static async Task<string> ExecuteFunctionAsync(FunctionResult function, b
180203
/// <param name="headers">Optional headers.</param>
181204
/// <param name="soapEnvelope">SOAP envelope in XML format.</param>
182205
/// <param name="logRequestAndResponse">Indicate whether the request and response should be logged..</param>
183-
/// <returns>Response content from the SOAP service as a string.</returns>
184-
private static async Task<string> CallSoapApiAsync(ApiItem apiDefinition,
185-
string endPoint,
186-
string soapAction,
187-
Dictionary<string, string> headers,
188-
string soapEnvelope,
189-
bool logRequestAndResponse)
206+
/// <returns>The API response.</returns>
207+
private static async Task<HttpResponseMessage> CallSoapApiAsync(ApiItem apiDefinition,
208+
string endPoint,
209+
string soapAction,
210+
Dictionary<string, string> headers,
211+
string soapEnvelope,
212+
bool logRequestAndResponse)
190213
{
191214
using (HttpClient client = new())
192215
{
193-
HttpRequestMessage request = new(HttpMethod.Post, apiDefinition.BaseUrl + endPoint)
216+
HttpRequestMessage request = new(HttpMethod.Post, apiDefinition.BaseUrl.TrimEnd('/') + endPoint)
194217
{
195218
Content = new StringContent(soapEnvelope, Encoding.UTF8, "text/xml")
196219
};
@@ -222,7 +245,7 @@ private static async Task<string> CallSoapApiAsync(ApiItem apiDefinition,
222245
await HttpLogs.LogResponseAsync(response);
223246
}
224247

225-
return await response.Content.ReadAsStringAsync();
248+
return response;
226249
}
227250
}
228251

@@ -236,14 +259,14 @@ private static async Task<string> CallSoapApiAsync(ApiItem apiDefinition,
236259
/// <param name="queryParams">Optional query parameters.</param>
237260
/// <param name="body">Request body (when applicable).</param>
238261
/// <param name="logRequestAndResponse">Indicate whether the request and response should be logged..</param>
239-
/// <returns>Response content from the API as a string.</returns>
240-
private static async Task<string> CallRestApiAsync(ApiItem apiDefinition,
241-
string endPoint,
242-
string method,
243-
Dictionary<string, string> headers,
244-
Dictionary<string, string> queryParams,
245-
string body,
246-
bool logRequestAndResponse)
262+
/// <returns>The API response.</returns>
263+
private static async Task<HttpResponseMessage> CallRestApiAsync(ApiItem apiDefinition,
264+
string endPoint,
265+
string method,
266+
Dictionary<string, string> headers,
267+
Dictionary<string, string> queryParams,
268+
string body,
269+
bool logRequestAndResponse)
247270
{
248271
using (HttpClient client = new())
249272
{
@@ -255,7 +278,7 @@ private static async Task<string> CallRestApiAsync(ApiItem apiDefinition,
255278
endPoint += (endPoint.Contains("?") ? "&" : "?") + queryString;
256279
}
257280

258-
HttpRequestMessage request = new(new HttpMethod(method), apiDefinition.BaseUrl + endPoint);
281+
HttpRequestMessage request = new(new HttpMethod(method), apiDefinition.BaseUrl.TrimEnd('/') + endPoint);
259282

260283
// Add headers, if provided
261284
if (headers != null)
@@ -285,7 +308,67 @@ private static async Task<string> CallRestApiAsync(ApiItem apiDefinition,
285308
await HttpLogs.LogResponseAsync(response);
286309
}
287310

288-
return await response.Content.ReadAsStringAsync();
311+
return response;
312+
}
313+
}
314+
315+
/// <summary>
316+
/// Formats the provided XML string with indentation and wraps it in code block syntax for display.
317+
/// </summary>
318+
/// <param name="xml">The XML string to be formatted.</param>
319+
/// <returns>A formatted XML string wrapped in code block syntax.</returns>
320+
private static string FormatXml(string xml)
321+
{
322+
try
323+
{
324+
if (string.IsNullOrWhiteSpace(xml))
325+
{
326+
return null;
327+
}
328+
329+
StringBuilder stringBuilder = new();
330+
331+
XDocument xmlDoc = XDocument.Parse(xml);
332+
333+
using (StringWriter stringWriter = new(stringBuilder))
334+
{
335+
using (XmlWriter xmlWriter = XmlWriter.Create(stringWriter, new XmlWriterSettings { Indent = true }))
336+
{
337+
xmlDoc.WriteTo(xmlWriter);
338+
}
339+
}
340+
341+
return string.Concat("```xml", Environment.NewLine, stringBuilder.ToString(), Environment.NewLine, "```");
342+
}
343+
catch (Exception)
344+
{
345+
return xml;
346+
}
347+
}
348+
349+
/// <summary>
350+
/// Formats a JSON string by parsing it and returning a prettified version enclosed in markdown code block syntax for JSON.
351+
/// </summary>
352+
/// <param name="json">The JSON string to be formatted.</param>
353+
/// <returns>
354+
/// A string containing the formatted JSON wrapped in markdown code block syntax.
355+
/// </returns>
356+
private static string FormatJson(string json)
357+
{
358+
try
359+
{
360+
if (string.IsNullOrWhiteSpace(json))
361+
{
362+
return null;
363+
}
364+
365+
JToken parsedJson = JToken.Parse(json);
366+
367+
return string.Concat("```json", Environment.NewLine, parsedJson.ToString(Newtonsoft.Json.Formatting.Indented), Environment.NewLine, "```");
368+
}
369+
catch (Exception)
370+
{
371+
return json;
289372
}
290373
}
291374

VisualChatGPTStudioShared/Agents/ApiAgent/ApiItem.cs

+5
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@ public class ApiItem
2222
/// </summary>
2323
public string BaseUrl { get; set; }
2424

25+
/// <summary>
26+
/// If true, all responses will be send to AI, when false, only HTTP status will be send.
27+
/// </summary>
28+
public bool SendResponsesToAI { get; set; }
29+
2530
/// <summary>
2631
/// Gets or sets the collection of API tag items.
2732
/// </summary>

VisualChatGPTStudioShared/Options/ApiAgent/OptionApiAgentWindow.xaml

+11
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,17 @@
6262
<TextBox x:Name="txtBaseUrl" Grid.Row="2" Grid.Column="2" FontSize="16" MaxLength="255"/>
6363
<Image Grid.Row="2" Grid.Column="4" VerticalAlignment="Center" ToolTip="Enter the base URL of the API." Source="pack://application:,,,/VisualChatGPTStudio;component/Resources/information.png" />
6464

65+
<!-- SEND RESPONSES TO AI -->
66+
<Label Content="Send Responses to AI:" Grid.Row="4" Grid.Column="0" />
67+
<StackPanel Grid.Row="4" Grid.Column="2" Orientation="Horizontal" HorizontalAlignment="Left">
68+
<CheckBox Name="chkSendReponsesToAI" Margin="0,0,10,0">
69+
<CheckBox.LayoutTransform>
70+
<ScaleTransform ScaleX="1.5" ScaleY="1.5"/>
71+
</CheckBox.LayoutTransform>
72+
</CheckBox>
73+
<Image ToolTip="If checked, all API responses will be forwarded to the AI so it can process and retain them in its context. Otherwise, the AI will only receive the HTTP status, and the responses will be displayed directly in the chat. This option is ideal if you want to protect data and save tokens." Source="pack://application:,,,/VisualChatGPTStudio;component/Resources/information.png" />
74+
</StackPanel>
75+
6576
<!-- TAGs -->
6677
<Button x:Name="btnInsertTag" Content="Insert a Key/Value" Click="btnInsertTag_Click" Grid.Row="4" Grid.Column="2" HorizontalAlignment="Right" Width="150" />
6778
<Label Content="Key/Values:" Grid.Row="6" Grid.Column="0" VerticalAlignment="Top" />

VisualChatGPTStudioShared/Options/ApiAgent/OptionApiAgentWindow.xaml.cs

+3
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ private void btnInsertApi_Click(object sender, RoutedEventArgs e)
128128
Name = txtIdentification.Text.Trim(),
129129
BaseUrl = txtBaseUrl.Text.Trim(),
130130
Tags = Tags.ToList(),
131+
SendResponsesToAI = chkSendReponsesToAI.IsChecked.Value,
131132
Definition = txtDefinition.Text.Trim()
132133
};
133134

@@ -139,6 +140,7 @@ private void btnInsertApi_Click(object sender, RoutedEventArgs e)
139140
txtIdentification.Clear();
140141
txtBaseUrl.Clear();
141142
txtDefinition.Clear();
143+
chkSendReponsesToAI.IsChecked = false;
142144
Tags.Clear();
143145
}
144146

@@ -153,6 +155,7 @@ private void btnEditApi_Click(object sender, MouseButtonEventArgs e)
153155
apiIdOnEdit = selectedApi.Id;
154156
txtIdentification.Text = selectedApi.Name;
155157
txtBaseUrl.Text = selectedApi.BaseUrl;
158+
chkSendReponsesToAI.IsChecked = selectedApi.SendResponsesToAI;
156159
txtDefinition.Text = selectedApi.Definition;
157160
Tags.Clear();
158161

VisualChatGPTStudioShared/ToolWindows/Turbo/ChatListControlItem.cs

+6-1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ public ChatListControlItem(AuthorEnum author, string message)
3030
{
3131
ImageSource = "pack://application:,,,/VisualChatGPTStudio;component/Resources/chatGPT.png";
3232
}
33+
else if (author == AuthorEnum.ApiResult)
34+
{
35+
ImageSource = "pack://application:,,,/VisualChatGPTStudio;component/Resources/api.png";
36+
}
3337
}
3438
}
3539

@@ -39,6 +43,7 @@ public enum AuthorEnum
3943
ChatGPT,
4044
ChatGPTCode,
4145
FunctionCall,
42-
FunctionRequest
46+
FunctionRequest,
47+
ApiResult
4348
}
4449
}

0 commit comments

Comments
 (0)