diff --git a/dotnet/src/Skills/Skills.Grpc/GrpcOperationRunner.cs b/dotnet/src/Skills/Skills.Grpc/GrpcOperationRunner.cs index bdca0c7ff558..621ec6719835 100644 --- a/dotnet/src/Skills/Skills.Grpc/GrpcOperationRunner.cs +++ b/dotnet/src/Skills/Skills.Grpc/GrpcOperationRunner.cs @@ -160,7 +160,7 @@ private object GenerateOperationRequest(GrpcOperation operation, Type type, IDic throw new GrpcOperationException($"No '{GrpcOperation.PayloadArgumentName}' argument representing gRPC request message is found for the '{operation.Name}' gRPC operation."); } - //Deserializing JOSN payload to gRPC request message + //Deserializing JSON payload to gRPC request message var instance = JsonSerializer.Deserialize(payload, type, new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); if (instance == null) { @@ -212,7 +212,7 @@ private static Type BuildGrpcOperationDataContractType(GrpcOperationDataContract setterIl.Emit(OpCodes.Stfld, fieldBuilder); setterIl.Emit(OpCodes.Ret); - //Registering the property get and set methods. + //Registering the property get and set methods. propertyBuilder.SetGetMethod(getterBuilder); propertyBuilder.SetSetMethod(setterBuilder); diff --git a/samples/java/JavaReferenceSkill/.gitignore b/samples/java/JavaReferenceSkill/.gitignore new file mode 100644 index 000000000000..fc3f89ced511 --- /dev/null +++ b/samples/java/JavaReferenceSkill/.gitignore @@ -0,0 +1,39 @@ +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### IntelliJ IDEA ### +.idea +.idea/modules.xml +.idea/jarRepositories.xml +.idea/compiler.xml +.idea/libraries/ +*.iws +*.iml +*.ipr + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store \ No newline at end of file diff --git a/samples/java/JavaReferenceSkill/README.md b/samples/java/JavaReferenceSkill/README.md new file mode 100644 index 000000000000..8a4306c51baf --- /dev/null +++ b/samples/java/JavaReferenceSkill/README.md @@ -0,0 +1,23 @@ +# Java Reference Skill gRPC Server +This is a sample Java gRPC server that can be invoked via SK's gRPC client as a Native Skill/Function. The purpose of this project is to demonstrate how Polyglot skills can be supported using either REST or gRPC. + +## Prerequisites +* Java 17 +* Maven + +## Build +To build the project, run the following command: +``` +mvn clean package +``` +To generate the gRPC classes, run the following command: +``` +mvn protobuf:compile +``` + +## Run +To run the project, run the following command: +``` +java -jar ./target/JavaReferenceSkill-1.0-SNAPSHOT-jar-with-dependencies.jar +``` + diff --git a/samples/java/JavaReferenceSkill/pom.xml b/samples/java/JavaReferenceSkill/pom.xml new file mode 100644 index 000000000000..9161cfc1cbe1 --- /dev/null +++ b/samples/java/JavaReferenceSkill/pom.xml @@ -0,0 +1,109 @@ + + + 4.0.0 + + com.microsoft.semantickernel.skills.random + JavaReferenceSkill + 1.0-SNAPSHOT + + + 17 + 17 + UTF-8 + 1.54.0 + 1.2 + 1.7.1 + 0.6.1 + 3.22.2 + 5.2.0 + + + + + io.grpc + grpc-protobuf + ${grpc.version} + + + io.grpc + grpc-stub + ${grpc.version} + + + io.grpc + grpc-testing + ${grpc.version} + + + io.grpc + grpc-netty-shaded + ${grpc.version} + + + org.mockito + mockito-core + ${mockito-core.version} + + + javax.annotation + javax.annotation-api + ${javax.annotation-api.version} + + + + + + + kr.motd.maven + os-maven-plugin + ${os-maven-plugin.version} + + + + + org.xolstice.maven.plugins + protobuf-maven-plugin + ${protobuf-maven-plugin.version} + + com.google.protobuf:protoc:${protoc.version}:exe:${os.detected.classifier} + grpc-java + io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier} + + + + + compile + compile-custom + + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + jar-with-dependencies + + + + com.microsoft.semantickernel.skills.random.Main + + + + + + make-assembly + package + + single + + + + + + + + \ No newline at end of file diff --git a/samples/java/JavaReferenceSkill/src/main/java/com/microsoft/semantickernel/skills/random/Main.java b/samples/java/JavaReferenceSkill/src/main/java/com/microsoft/semantickernel/skills/random/Main.java new file mode 100644 index 000000000000..6719a9aefb59 --- /dev/null +++ b/samples/java/JavaReferenceSkill/src/main/java/com/microsoft/semantickernel/skills/random/Main.java @@ -0,0 +1,28 @@ +package com.microsoft.semantickernel.skills.random; + +import io.grpc.Server; +import io.grpc.ServerBuilder; + +import java.util.logging.Logger; + +public class Main { + + private static final int PORT = 50051; + + public static void main(String[] args) { + Logger logger = java.util.logging.Logger.getLogger(Main.class.getName()); + + Server server = ServerBuilder.forPort(PORT) + .addService(new RandomActivitySkill()).build(); + + System.out.println("Starting server..."); + try { + server.start(); + System.out.println("gRPC Server for random activity started on port " + PORT); + server.awaitTermination(); + } catch (Exception e) { + logger.severe("Error with request: " + e.getMessage()); + throw new RuntimeException(e); + } + } +} \ No newline at end of file diff --git a/samples/java/JavaReferenceSkill/src/main/java/com/microsoft/semantickernel/skills/random/RandomActivitySkill.java b/samples/java/JavaReferenceSkill/src/main/java/com/microsoft/semantickernel/skills/random/RandomActivitySkill.java new file mode 100644 index 000000000000..7036a2dc8976 --- /dev/null +++ b/samples/java/JavaReferenceSkill/src/main/java/com/microsoft/semantickernel/skills/random/RandomActivitySkill.java @@ -0,0 +1,42 @@ +package com.microsoft.semantickernel.skills.random; + +import io.grpc.stub.StreamObserver; +import reference_skill.ActivityOuterClass; +import reference_skill.RandomActivitySkillGrpc; + +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.util.concurrent.CompletableFuture; +import java.util.logging.Logger; + +public class RandomActivitySkill extends RandomActivitySkillGrpc.RandomActivitySkillImplBase { + + public static final String API_ACTIVITY_URL = "https://www.boredapi.com/api/activity"; + + /** + *
+     * GetRandomActivity is an RPC method that retrieves a random activity from an API.
+     * 
+ * + * @param request + * @param responseObserver + */ + @Override + public void getRandomActivity(ActivityOuterClass.GetRandomActivityRequest request, StreamObserver responseObserver) { + Logger logger = java.util.logging.Logger.getLogger(this.getClass().getName()); + HttpClient httpClient = HttpClient.newHttpClient(); + HttpRequest httpRequest = HttpRequest.newBuilder() + .uri(URI.create(API_ACTIVITY_URL)) + .build(); + try { + CompletableFuture> response = httpClient.sendAsync(httpRequest, HttpResponse.BodyHandlers.ofString()); + logger.info("Response: " + response.get().body()); + responseObserver.onNext(ActivityOuterClass.GetRandomActivityResponse.newBuilder().setActivity(response.get().body()).build()); + responseObserver.onCompleted(); + } catch (Exception e) { + logger.severe("Error with request: " + e.getMessage()); + } + } +} diff --git a/samples/java/JavaReferenceSkill/src/main/proto/activity.proto b/samples/java/JavaReferenceSkill/src/main/proto/activity.proto new file mode 100644 index 000000000000..ac09fb2b676f --- /dev/null +++ b/samples/java/JavaReferenceSkill/src/main/proto/activity.proto @@ -0,0 +1,30 @@ +syntax = "proto3"; + +package reference_skill; + +// GetRandomActivityRequest is a message that contains input for the GetRandomActivity RPC method. +message GetRandomActivityRequest { + string input = 1; // Input is a hobby that is use to generate a random activity. +} + +// GetRandomActivityResponse is a message that contains the activity returned by the GetRandomActivity RPC method. +message GetRandomActivityResponse { + string activity = 1; // Activity is a description of the random activity. +} + +// RandomActivitySkill is a service that provides methods related to random activities. +service RandomActivitySkill { + // GetRandomActivity is an RPC method that retrieves a random activity from an API. + rpc GetRandomActivity (GetRandomActivityRequest) returns (GetRandomActivityResponse); +} + +// Activity is a message that represents an activity with its various properties. +message Activity { + string activity = 1; // A description of the activity. + string type = 2; // The type or category of the activity. + int32 participants = 3; // The number of participants required for the activity. + double price = 4; // The cost associated with the activity, from 0 (free) to 1 (most expensive). + string link = 5; // A URL providing more information about the activity. + string key = 6; // A unique identifier for the activity. + float accessibility = 7; // The accessibility of the activity, from 0 (most accessible) to 1 (least accessible). +} diff --git a/samples/java/JavaReferenceSkill/src/test/java/com/microsoft/semantickernel/skills/random/RandomActivitySkillTest.java b/samples/java/JavaReferenceSkill/src/test/java/com/microsoft/semantickernel/skills/random/RandomActivitySkillTest.java new file mode 100644 index 000000000000..fdc8f7268e24 --- /dev/null +++ b/samples/java/JavaReferenceSkill/src/test/java/com/microsoft/semantickernel/skills/random/RandomActivitySkillTest.java @@ -0,0 +1,51 @@ +package com.microsoft.semantickernel.skills.random; + +import io.grpc.stub.StreamObserver; +import io.grpc.testing.GrpcServerRule; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import reference_skill.ActivityOuterClass; +import reference_skill.RandomActivitySkillGrpc; + +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.util.concurrent.CompletableFuture; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +public class RandomActivitySkillTest { + + @Rule + public GrpcServerRule grpcServerRule = new GrpcServerRule().directExecutor(); + + private RandomActivitySkillGrpc.RandomActivitySkillBlockingStub blockingStub; + + @Before + public void setUp() { + grpcServerRule.getServiceRegistry().addService(new RandomActivitySkill()); + blockingStub = RandomActivitySkillGrpc.newBlockingStub(grpcServerRule.getChannel()); + } + + @Test + public void testGetRandomActivity() throws Exception { + HttpClient httpClient = mock(HttpClient.class); + HttpResponse httpResponse = mock(HttpResponse.class); + CompletableFuture> responseFuture = CompletableFuture.completedFuture(httpResponse); + + when(httpClient.sendAsync(any(HttpRequest.class), any(HttpResponse.BodyHandler.class))).thenReturn(responseFuture); + when(httpResponse.body()).thenReturn("{\"activity\":\"Test Activity\"}"); + + RandomActivitySkill randomActivitySkill = new RandomActivitySkill() { + }; + + ActivityOuterClass.GetRandomActivityRequest request = ActivityOuterClass.GetRandomActivityRequest.newBuilder().build(); + StreamObserver responseObserver = mock(StreamObserver.class); + randomActivitySkill.getRandomActivity(request, responseObserver); + + verify(responseObserver).onNext(any(ActivityOuterClass.GetRandomActivityResponse.class)); + verify(responseObserver).onCompleted(); + } +}