Skip to content

API Chat for GraphQL APIs implementation #13098

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 18 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
f85e96a
API prepare done for REST and GRAPHQL
Nayanatharapmc Apr 21, 2025
9f49021
Add APIChatGraphQLSdl model and integrate into APIChatTestInitializer…
Nayanatharapmc Apr 24, 2025
bc054e0
Add apiType field to APIChatTestExecutionInfo and APIChatTestInitiali…
Nayanatharapmc May 2, 2025
f81fcff
Refactor API type retrieval in ApisApiServiceImpl to streamline error…
Nayanatharapmc May 9, 2025
ddbda0e
Remove apiType from request body and add it as a query parameter
Nayanatharapmc May 12, 2025
8a70563
Update API type parameter description in APIConsumer interface
Nayanatharapmc May 12, 2025
017c0ce
Remove unnecessary blank line in APIChatTestExecutionInfo class
Nayanatharapmc May 12, 2025
16475b4
Remove unnecessary blank line in APIChatTestInitializerInfo class
Nayanatharapmc May 12, 2025
50be8ea
Remove commented check for GraphQL API type in APIConsumerImpl
Nayanatharapmc May 12, 2025
1a1618b
Add copyright notice and update GraphQL schema examples in APIChat cl…
Nayanatharapmc May 15, 2025
0828bb3
Update GraphQL API schema example for clarity and formatting
Nayanatharapmc May 15, 2025
4c3e299
Add new field 'schemaDefinition' to ApiChatRequest and ApiChatRespons…
Nayanatharapmc May 19, 2025
522602c
Log error message before throwing APIManagementException for unsuppor…
Nayanatharapmc May 19, 2025
688096e
Fix copyright notice URL in APIChatGraphQLSdl.java
Nayanatharapmc May 20, 2025
435e199
Add 'required' field for 'tools' in Enriched API spec and DTO
Nayanatharapmc May 20, 2025
ea65be0
Remove unused import for URLEncoder in APIUtil.java
Nayanatharapmc May 20, 2025
490e80c
Refactor URI construction in invokeApiChatExecute and invokeApiChatPr…
Nayanatharapmc May 21, 2025
251b96e
Remove unsupported API type check for API Chat in APIConsumerImpl
Nayanatharapmc May 22, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -837,11 +837,12 @@ void resetApplicationThrottlePolicy(String applicationId, String userId, String
* Returns the API Chat execute call response as a string
*
* @param apiChatRequestId Request UUID
* @param apiType Type of the API being tested
* @param requestPayload Request payload to be used for the AI service execute call
* @return execution response as a string
* @throws APIManagementException if execute call failed
*/
String invokeApiChatExecute(String apiChatRequestId, String requestPayload) throws APIManagementException;
String invokeApiChatExecute(String apiChatRequestId, String apiType, String requestPayload) throws APIManagementException;

/**
* Returns the API Chat prepare call response as a string
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package org.wso2.carbon.apimgt.api.model;

public class APIChatGraphQLSdl {
private String sdl;

public String getSdl() {
return sdl;
}

public void setSdl(String sdl) {
this.sdl = sdl;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
public class APIChatTestInitializerInfo {
private String command = null;
private APIChatAPISpec apiSpec = null;
private APIChatGraphQLSdl sdl = null;

public String getCommand() {
return command;
Expand All @@ -40,4 +41,12 @@ public APIChatAPISpec getApiSpec() {
public void setApiSpec(APIChatAPISpec apiSpec) {
this.apiSpec = apiSpec;
}

public APIChatGraphQLSdl getSdl() {
return sdl;
}

public void setSdl(APIChatGraphQLSdl sdl){
this.sdl = sdl;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,8 @@
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
Expand Down Expand Up @@ -3355,35 +3357,46 @@ public String getOpenAPIDefinition(String apiId, String organization) throws API
}

@Override
public String invokeApiChatExecute(String apiChatRequestId, String requestPayload) throws APIManagementException {
public String invokeApiChatExecute(String apiChatRequestId, String apiType, String requestPayload) throws APIManagementException {
ApiChatConfigurationDTO configDto = ServiceReferenceHolder.getInstance().getAPIManagerConfigurationService()
.getAPIManagerConfiguration().getApiChatConfigurationDto();
String resourceWithQueryParam = configDto.getExecuteResource() + "?apiType=" + URLEncoder.encode(apiType, StandardCharsets.UTF_8);
if (configDto.isKeyProvided()) {
return APIUtil.invokeAIService(configDto.getEndpoint(), configDto.getTokenEndpoint(), configDto.getKey(),
configDto.getExecuteResource(), requestPayload, apiChatRequestId);
resourceWithQueryParam, requestPayload, apiChatRequestId);
}
return APIUtil.invokeAIService(configDto.getEndpoint(), null, configDto.getAccessToken(),
configDto.getExecuteResource(), requestPayload, apiChatRequestId);
resourceWithQueryParam, requestPayload, apiChatRequestId);
}

@Override
public String invokeApiChatPrepare(String apiId, String apiChatRequestId, String organization)
throws APIManagementException {
try {
// Generate the payload for prepare call
String apiType = ApiMgtDAO.getInstance().getAPITypeFromUUID(apiId);
ObjectMapper objectMapper = new ObjectMapper();
JsonNode openAPIDefinitionJsonNode = objectMapper.readTree(getOpenAPIDefinition(apiId, organization));
ObjectNode payload = objectMapper.createObjectNode();
payload.set(APIConstants.OPEN_API, openAPIDefinitionJsonNode);

if (APIConstants.APITransportType.GRAPHQL.name().equalsIgnoreCase(apiType)) {
String graphQLSchema = getGraphqlSchemaDefinition(apiId, organization);
payload.put(APIConstants.GRAPHQL_SCHEMA, graphQLSchema);
} else if (APIConstants.APITransportType.HTTP.name().equalsIgnoreCase(apiType)) {
JsonNode openAPIDefinitionJsonNode = objectMapper.readTree(getOpenAPIDefinition(apiId, organization));
payload.set(APIConstants.OPEN_API, openAPIDefinitionJsonNode);
} else {
throw new APIManagementException("Unsupported API type for API Chat: " + apiType);
}
ApiChatConfigurationDTO configDto = ServiceReferenceHolder.getInstance().getAPIManagerConfigurationService()
.getAPIManagerConfiguration().getApiChatConfigurationDto();
String resourceWithQueryParam = configDto.getPrepareResource() + "?apiType=" + URLEncoder.encode(apiType, StandardCharsets.UTF_8);

if (configDto.isKeyProvided()) {
return APIUtil.invokeAIService(configDto.getEndpoint(), configDto.getTokenEndpoint(), configDto.getKey(),
configDto.getPrepareResource(), payload.toString(), apiChatRequestId);
resourceWithQueryParam, payload.toString(), apiChatRequestId);
}
return APIUtil.invokeAIService(configDto.getEndpoint(), null, configDto.getAccessToken(),
configDto.getPrepareResource(), payload.toString(), apiChatRequestId);
resourceWithQueryParam, payload.toString(), apiChatRequestId);
} catch (JsonProcessingException e) {
String error = "Error while parsing OpenAPI definition of API ID: " + apiId + " to JSON";
log.error(error, e);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,7 @@
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLEncoder;
import java.net.URLDecoder;
import java.net.UnknownHostException;
import java.nio.charset.Charset;
Expand Down Expand Up @@ -11213,7 +11214,8 @@ public static String invokeAIService(String endpoint, String tokenEndpoint, Stri
tokenEndpoint, key, requestId, payload)){
int statusCode = response.getStatusLine().getStatusCode();
String responseStr = EntityUtils.toString(response.getEntity());
if (statusCode == HttpStatus.SC_CREATED) {
// STATUS CODE OK OR CREATED
if (statusCode == HttpStatus.SC_OK || statusCode == HttpStatus.SC_CREATED) {
return responseStr;
} else if (statusCode == HttpStatus.SC_UNAUTHORIZED) {
String errorMsg = "Invalid credentials used when invoking the AI service.";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6142,6 +6142,22 @@ components:
schema:
type: "string"
format: "string"
sdl:
type: string
description: GraphQL API schema definition
example: '
schema {
query: Query
}
type Query {
hero(id: ID!): Character
allHeroes: [Character]
}
type Character {
id: ID!
name: String!
appearsIn: [String]
}'
response:
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Invalid YAML literal for multiline example
The example value for the new sdl property in ApiChatRequest.apiSpec is wrapped in single quotes but contains actual newlines, which isn’t valid YAML. You should use a block scalar (|) to represent a multiline string.

Proposed fix:

@@ -6139,7 +6139,7 @@
             tools:
               type: array
               description: Extracted Http tools from the OpenAPI specification
-              example: '…'
+              example: |
+                        schema {
+                          query: Query
+                        }
+                        type Query {
+                          hero(id: ID!): Character
+                          allHeroes: [Character]
+                        }
+                        type Character {
+                          id: ID!
+                          name: String!
+                          appearsIn: [String]
+                        }
             sdl:
               type: string
               description: GraphQL API schema definition
-              example: '
-                        schema {
-                          query: Query
-                        }
-                        type Query {
-                          hero(id: ID!): Character
-                          allHeroes: [Character]
-                        }
-                        type Character {
-                          id: ID!
-                          name: String!
-                          appearsIn: [String]
-                        }'
+              example: |
+                        schema {
+                          query: Query
+                        }
+                        type Query {
+                          hero(id: ID!): Character
+                          allHeroes: [Character]
+                        }
+                        type Character {
+                          id: ID!
+                          name: String!
+                          appearsIn: [String]
+                        }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
sdl:
type: string
description: GraphQL API schema definition
example: '
schema {
query: Query
}
type Query {
hero(id: ID!): Character
allHeroes: [Character]
}
type Character {
id: ID!
name: String!
appearsIn: [String]
}'
response:
sdl:
type: string
description: GraphQL API schema definition
example: |
schema {
query: Query
}
type Query {
hero(id: ID!): Character
allHeroes: [Character]
}
type Character {
id: ID!
name: String!
appearsIn: [String]
}
response:

type: object
properties:
Expand Down Expand Up @@ -6175,8 +6191,6 @@ components:
apiSpec:
type: object
title: Enriched API spec
required:
- tools
properties:
serviceUrl:
type: string
Expand All @@ -6202,6 +6216,10 @@ components:
schema:
type: "string"
format: "string"
sdl:
type: string
description: Processed GraphQL API schema definition (for GraphQL APIs)
example: "schema { query: Query } type Query { hero(id: ID!): Character allHeroes: [Character] } type Character { id: ID! name: String! appearsIn: [String] }"
taskStatus:
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Use block scalar for the example in ApiChatResponse.apiSpec.sdl
The example for the sdl property in the response schema is written as a single-line quoted string containing many types. For consistency and valid YAML, convert it to a block scalar.

Proposed fix:

@@ -6217,7 +6217,7 @@
             tools:
               type: array
               description: Extracted Http tools from the OpenAPI specification
-              example: "schema { query: Query } type Query { hero(id: ID!): Character allHeroes: [Character] } type Character { id: ID! name: String! appearsIn: [String] }"
+              example: |
+                        schema {
+                          query: Query
+                        }
+                        type Query {
+                          hero(id: ID!): Character
+                          allHeroes: [Character]
+                        }
+                        type Character {
+                          id: ID!
+                          name: String!
+                          appearsIn: [String]
+                        }
             sdl:
               type: string
               description: Processed GraphQL API schema definition (for GraphQL APIs)

Committable suggestion skipped: line range outside the PR's diff.

type: string
description: Task status (IN_PROGRESS, TERMINATED or COMPLETED)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ public class ApiChatRequestApiSpecDTO {

private String serviceUrl = null;
private List<Object> tools = new ArrayList<Object>();
private String sdl = null;

/**
* Service URL of API if any
Expand Down Expand Up @@ -61,6 +62,24 @@ public void setTools(List<Object> tools) {
this.tools = tools;
}

/**
* GraphQL API schema definition
**/
public ApiChatRequestApiSpecDTO sdl(String sdl) {
this.sdl = sdl;
return this;
}


@ApiModelProperty(example = " schema { query: Query } type Query { hero(id: ID!): Character allHeroes: [Character] } type Character { id: ID! name: String! appearsIn: [String] }", value = "GraphQL API schema definition")
@JsonProperty("sdl")
public String getSdl() {
return sdl;
}
public void setSdl(String sdl) {
this.sdl = sdl;
}


@Override
public boolean equals(java.lang.Object o) {
Expand All @@ -72,12 +91,13 @@ public boolean equals(java.lang.Object o) {
}
ApiChatRequestApiSpecDTO apiChatRequestApiSpec = (ApiChatRequestApiSpecDTO) o;
return Objects.equals(serviceUrl, apiChatRequestApiSpec.serviceUrl) &&
Objects.equals(tools, apiChatRequestApiSpec.tools);
Objects.equals(tools, apiChatRequestApiSpec.tools) &&
Objects.equals(sdl, apiChatRequestApiSpec.sdl);
}

@Override
public int hashCode() {
return Objects.hash(serviceUrl, tools);
return Objects.hash(serviceUrl, tools, sdl);
}

@Override
Expand All @@ -87,6 +107,7 @@ public String toString() {

sb.append(" serviceUrl: ").append(toIndentedString(serviceUrl)).append("\n");
sb.append(" tools: ").append(toIndentedString(tools)).append("\n");
sb.append(" sdl: ").append(toIndentedString(sdl)).append("\n");
sb.append("}");
return sb.toString();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ public class EnrichedAPISpecDTO {

private String serviceUrl = null;
private List<Object> tools = new ArrayList<Object>();
private String sdl = null;

/**
* Extracted service URL from the OpenAPI specification if there is any
Expand Down Expand Up @@ -52,16 +53,33 @@ public EnrichedAPISpecDTO tools(List<Object> tools) {
}


@ApiModelProperty(required = true, value = "Extracted Http tools from the OpenAPI specification")
@ApiModelProperty(value = "Extracted Http tools from the OpenAPI specification")
@JsonProperty("tools")
@NotNull
public List<Object> getTools() {
return tools;
}
public void setTools(List<Object> tools) {
this.tools = tools;
}

/**
* Processed GraphQL API schema definition (for GraphQL APIs)
**/
public EnrichedAPISpecDTO sdl(String sdl) {
this.sdl = sdl;
return this;
}


@ApiModelProperty(example = "schema { query: Query } type Query { hero(id: ID!): Character allHeroes: [Character] } type Character { id: ID! name: String! appearsIn: [String] }", value = "Processed GraphQL API schema definition (for GraphQL APIs)")
@JsonProperty("sdl")
public String getSdl() {
return sdl;
}
public void setSdl(String sdl) {
this.sdl = sdl;
}


@Override
public boolean equals(java.lang.Object o) {
Expand All @@ -73,12 +91,13 @@ public boolean equals(java.lang.Object o) {
}
EnrichedAPISpecDTO enrichedAPISpec = (EnrichedAPISpecDTO) o;
return Objects.equals(serviceUrl, enrichedAPISpec.serviceUrl) &&
Objects.equals(tools, enrichedAPISpec.tools);
Objects.equals(tools, enrichedAPISpec.tools) &&
Objects.equals(sdl, enrichedAPISpec.sdl);
}

@Override
public int hashCode() {
return Objects.hash(serviceUrl, tools);
return Objects.hash(serviceUrl, tools, sdl);
}

@Override
Expand All @@ -88,6 +107,7 @@ public String toString() {

sb.append(" serviceUrl: ").append(toIndentedString(serviceUrl)).append("\n");
sb.append(" tools: ").append(toIndentedString(tools)).append("\n");
sb.append(" sdl: ").append(toIndentedString(sdl)).append("\n");
sb.append("}");
return sb.toString();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,22 +28,7 @@
import org.wso2.carbon.apimgt.api.APIConsumer;
import org.wso2.carbon.apimgt.api.APIManagementException;
import org.wso2.carbon.apimgt.api.ExceptionCodes;
import org.wso2.carbon.apimgt.api.model.API;
import org.wso2.carbon.apimgt.api.model.APIChatAPISpec;
import org.wso2.carbon.apimgt.api.model.APIChatExecutionResponse;
import org.wso2.carbon.apimgt.api.model.APIChatTestExecutionInfo;
import org.wso2.carbon.apimgt.api.model.APIChatTestInitializerInfo;
import org.wso2.carbon.apimgt.api.model.APIIdentifier;
import org.wso2.carbon.apimgt.api.model.APIRating;
import org.wso2.carbon.apimgt.api.model.ApiTypeWrapper;
import org.wso2.carbon.apimgt.api.model.Comment;
import org.wso2.carbon.apimgt.api.model.CommentList;
import org.wso2.carbon.apimgt.api.model.Documentation;
import org.wso2.carbon.apimgt.api.model.DocumentationContent;
import org.wso2.carbon.apimgt.api.model.Environment;
import org.wso2.carbon.apimgt.api.model.OrganizationInfo;
import org.wso2.carbon.apimgt.api.model.ResourceFile;
import org.wso2.carbon.apimgt.api.model.Tier;
import org.wso2.carbon.apimgt.api.model.*;
import org.wso2.carbon.apimgt.api.model.graphql.queryanalysis.GraphqlComplexityInfo;
import org.wso2.carbon.apimgt.api.model.graphql.queryanalysis.GraphqlSchemaType;
import org.wso2.carbon.apimgt.api.model.webhooks.Topic;
Expand Down Expand Up @@ -340,17 +325,23 @@ public Response apiChatPost(String apiId, String apiChatAction, ApiChatRequestDT
apiChatRequestDTO.getCommand()) && apiChatRequestDTO.getApiSpec() != null;
boolean isTestExecutionRequest = apiChatRequestDTO.getResponse() != null;
String requestPayload; // Request payload for Choreo deployed API Chat Agent
String apiType = apiConsumer.getLightweightAPIByUUID(apiId,
RestApiUtil.getValidatedOrganization(messageContext)).getType();

if (isTestInitializationRequest) {
ApiChatRequestApiSpecDTO specDTO = apiChatRequestDTO.getApiSpec();
APIChatAPISpec apiSpec = new APIChatAPISpec();
apiSpec.setServiceUrl(specDTO.getServiceUrl());
apiSpec.setTools(specDTO.getTools());

APIChatTestInitializerInfo initializerInfo = new APIChatTestInitializerInfo();
initializerInfo.setCommand(apiChatRequestDTO.getCommand());
initializerInfo.setApiSpec(apiSpec);

if (apiType.equalsIgnoreCase(APIConstants.GRAPHQL_API)) {
APIChatGraphQLSdl apiSdl = new APIChatGraphQLSdl();
apiSdl.setSdl(specDTO.getSdl());
initializerInfo.setSdl(apiSdl);
} else {
APIChatAPISpec apiSpec = new APIChatAPISpec();
apiSpec.setServiceUrl(specDTO.getServiceUrl());
apiSpec.setTools(specDTO.getTools());
initializerInfo.setApiSpec(apiSpec);
}
// Generate the payload for Choreo deployed API Chat Agent
ObjectMapper payloadMapper = new ObjectMapper();
requestPayload = payloadMapper.writeValueAsString(initializerInfo);
Expand All @@ -376,7 +367,7 @@ public Response apiChatPost(String apiId, String apiChatAction, ApiChatRequestDT
return null;
}

String executionResponse = apiConsumer.invokeApiChatExecute(apiChatRequestId, requestPayload);
String executionResponse = apiConsumer.invokeApiChatExecute(apiChatRequestId, apiType, requestPayload);
ObjectMapper responseMapper = new ObjectMapper();
ApiChatResponseDTO responseDTO = responseMapper.readValue(executionResponse,
ApiChatResponseDTO.class);
Expand Down
Loading
Loading