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 13 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,31 @@
/*
* Copyright (c) 2025, WSO2 LLC. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 LLC. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

package org.wso2.carbon.apimgt.api.model;

public class APIChatGraphQLSdl {
private String schemaDefinition;

public String getSchemaDefinition() {
return schemaDefinition;
}

public void setSchemaDefinition(String schemaDefinition) {
this.schemaDefinition = schemaDefinition;
}
}
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 schemaDefinition = 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 getSchemaDefinition() {
return schemaDefinition;
}

public void setSchemaDefinition(APIChatGraphQLSdl schemaDefinition){
this.schemaDefinition = schemaDefinition;
}
}
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,48 @@ 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 {
String errorMessage = "Unsupported API type for API Chat: " + apiType;
log.error(errorMessage);
throw new APIManagementException(errorMessage);
}
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,10 @@ components:
schema:
type: "string"
format: "string"
schemaDefinition:
type: string
description: GraphQL API schema definition
example: "schema {\n query: Query\n}\n\n# The query type, represents all of the entry points into our object graph\ntype Query {\n hero(id: ID!): Character\n allHeroes: [Character]\n}\n\n# A character from the Star Wars universe\ntype Character {\n # The unique identifier for the character\n id: ID!\n\n # The name of the character\n name: String!\n\n # The list of episodes the character appears in\n appearsIn: [String]\n}"
response:
type: object
properties:
Expand Down Expand Up @@ -6175,8 +6179,6 @@ components:
apiSpec:
type: object
title: Enriched API spec
required:
- tools
properties:
serviceUrl:
type: string
Expand All @@ -6202,6 +6204,10 @@ components:
schema:
type: "string"
format: "string"
schemaDefinition:
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:
type: string
description: Task status (IN_PROGRESS, TERMINATED or COMPLETED)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ public class ApiChatRequestDTO {
private String apiChatRequestId = null;
private String command = null;
private ApiChatRequestApiSpecDTO apiSpec = null;
private String schemaDefinition = null;
private ApiChatRequestResponseDTO response = null;

/**
Expand Down Expand Up @@ -81,6 +82,24 @@ public void setApiSpec(ApiChatRequestApiSpecDTO apiSpec) {
this.apiSpec = apiSpec;
}

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


@ApiModelProperty(example = "schema { query: Query } # The query type, represents all of the entry points into our object graph type Query { hero(id: ID!): Character allHeroes: [Character] } # A character from the Star Wars universe type Character { # The unique identifier for the character id: ID! # The name of the character name: String! # The list of episodes the character appears in appearsIn: [String] }", value = "GraphQL API schema definition")
@JsonProperty("schemaDefinition")
public String getSchemaDefinition() {
return schemaDefinition;
}
public void setSchemaDefinition(String schemaDefinition) {
this.schemaDefinition = schemaDefinition;
}

/**
**/
public ApiChatRequestDTO response(ApiChatRequestResponseDTO response) {
Expand Down Expand Up @@ -112,12 +131,13 @@ public boolean equals(java.lang.Object o) {
return Objects.equals(apiChatRequestId, apiChatRequest.apiChatRequestId) &&
Objects.equals(command, apiChatRequest.command) &&
Objects.equals(apiSpec, apiChatRequest.apiSpec) &&
Objects.equals(schemaDefinition, apiChatRequest.schemaDefinition) &&
Objects.equals(response, apiChatRequest.response);
}

@Override
public int hashCode() {
return Objects.hash(apiChatRequestId, command, apiSpec, response);
return Objects.hash(apiChatRequestId, command, apiSpec, schemaDefinition, response);
}

@Override
Expand All @@ -128,6 +148,7 @@ public String toString() {
sb.append(" apiChatRequestId: ").append(toIndentedString(apiChatRequestId)).append("\n");
sb.append(" command: ").append(toIndentedString(command)).append("\n");
sb.append(" apiSpec: ").append(toIndentedString(apiSpec)).append("\n");
sb.append(" schemaDefinition: ").append(toIndentedString(schemaDefinition)).append("\n");
sb.append(" response: ").append(toIndentedString(response)).append("\n");
sb.append("}");
return sb.toString();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
public class ApiChatResponseDTO {

private EnrichedAPISpecDTO apiSpec = null;
private String schemaDefinition = null;

@XmlType(name="TaskStatusEnum")
@XmlEnum(String.class)
Expand Down Expand Up @@ -81,6 +82,24 @@ public void setApiSpec(EnrichedAPISpecDTO apiSpec) {
this.apiSpec = apiSpec;
}

/**
* Processed GraphQL API schema definition (for GraphQL APIs)
**/
public ApiChatResponseDTO schemaDefinition(String schemaDefinition) {
this.schemaDefinition = schemaDefinition;
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("schemaDefinition")
public String getSchemaDefinition() {
return schemaDefinition;
}
public void setSchemaDefinition(String schemaDefinition) {
this.schemaDefinition = schemaDefinition;
}

/**
* Task status (IN_PROGRESS, TERMINATED or COMPLETED)
**/
Expand Down Expand Up @@ -165,6 +184,7 @@ public boolean equals(java.lang.Object o) {
}
ApiChatResponseDTO apiChatResponse = (ApiChatResponseDTO) o;
return Objects.equals(apiSpec, apiChatResponse.apiSpec) &&
Objects.equals(schemaDefinition, apiChatResponse.schemaDefinition) &&
Objects.equals(taskStatus, apiChatResponse.taskStatus) &&
Objects.equals(resource, apiChatResponse.resource) &&
Objects.equals(result, apiChatResponse.result) &&
Expand All @@ -173,7 +193,7 @@ public boolean equals(java.lang.Object o) {

@Override
public int hashCode() {
return Objects.hash(apiSpec, taskStatus, resource, result, queries);
return Objects.hash(apiSpec, schemaDefinition, taskStatus, resource, result, queries);
}

@Override
Expand All @@ -182,6 +202,7 @@ public String toString() {
sb.append("class ApiChatResponseDTO {\n");

sb.append(" apiSpec: ").append(toIndentedString(apiSpec)).append("\n");
sb.append(" schemaDefinition: ").append(toIndentedString(schemaDefinition)).append("\n");
sb.append(" taskStatus: ").append(toIndentedString(taskStatus)).append("\n");
sb.append(" resource: ").append(toIndentedString(resource)).append("\n");
sb.append(" result: ").append(toIndentedString(result)).append("\n");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,8 @@ 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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
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.APIChatGraphQLSdl;
import org.wso2.carbon.apimgt.api.model.APIChatTestExecutionInfo;
import org.wso2.carbon.apimgt.api.model.APIChatTestInitializerInfo;
import org.wso2.carbon.apimgt.api.model.APIIdentifier;
Expand Down Expand Up @@ -337,20 +338,27 @@ public Response apiChatPost(String apiId, String apiChatAction, ApiChatRequestDT
APIConsumer apiConsumer = RestApiCommonUtil.getLoggedInUserConsumer();
String apiChatRequestId = apiChatRequestDTO.getApiChatRequestId();
boolean isTestInitializationRequest = !StringUtils.isEmpty(
apiChatRequestDTO.getCommand()) && apiChatRequestDTO.getApiSpec() != null;
apiChatRequestDTO.getCommand()) && (apiChatRequestDTO.getApiSpec() != null | apiChatRequestDTO.getSchemaDefinition() != 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)) {
String schemaDefinition = apiChatRequestDTO.getSchemaDefinition();
APIChatGraphQLSdl apiSdl = new APIChatGraphQLSdl();
apiSdl.setSchemaDefinition(schemaDefinition);
initializerInfo.setSchemaDefinition(apiSdl);
} else {
ApiChatRequestApiSpecDTO specDTO = apiChatRequestDTO.getApiSpec();
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 +384,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
Original file line number Diff line number Diff line change
Expand Up @@ -6142,6 +6142,10 @@ components:
schema:
type: "string"
format: "string"
schemaDefinition:
type: string
description: GraphQL API schema definition
example: "schema {\n query: Query\n}\n\n# The query type, represents all of the entry points into our object graph\ntype Query {\n hero(id: ID!): Character\n allHeroes: [Character]\n}\n\n# A character from the Star Wars universe\ntype Character {\n # The unique identifier for the character\n id: ID!\n\n # The name of the character\n name: String!\n\n # The list of episodes the character appears in\n appearsIn: [String]\n}"
response:
type: object
properties:
Expand Down Expand Up @@ -6175,8 +6179,6 @@ components:
apiSpec:
type: object
title: Enriched API spec
required:
- tools
properties:
serviceUrl:
type: string
Expand All @@ -6202,6 +6204,10 @@ components:
schema:
type: "string"
format: "string"
schemaDefinition:
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:
type: string
description: Task status (IN_PROGRESS, TERMINATED or COMPLETED)
Expand Down
Loading