From ed651d61d498c61ae83f19f2d9bd1537b1c358ac Mon Sep 17 00:00:00 2001 From: Milder Hernandez Cagua Date: Thu, 17 Apr 2025 09:05:00 -0700 Subject: [PATCH 01/12] Add draft changes for agents --- agents/semantickernel-agents-core/pom.xml | 25 ++ .../chatcompletion/ChatCompletionAgent.java | 238 ++++++++++++++++++ .../ChatHistoryAgentThread.java | 64 +++++ agents/semantickernel-agents-openai/pom.xml | 26 ++ pom.xml | 2 + .../semantickernel-syntax-examples/pom.xml | 5 + .../samples/plugins/github/GitHubModel.java | 219 ++++++++++++++++ .../samples/plugins/github/GitHubPlugin.java | 165 ++++++++++++ .../agents/CompletionsAgent.java | 145 +++++++++++ .../semantickernel/agents/Agent.java | 60 +++++ .../agents/AgentInvokeOptions.java | 126 ++++++++++ .../agents/AgentResponseItem.java | 19 ++ .../semantickernel/agents/AgentThread.java | 45 ++++ .../agents/BaseAgentThread.java | 23 ++ .../semantickernel/agents/KernelAgent.java | 139 ++++++++++ .../services/chatcompletion/ChatHistory.java | 7 + semantickernel-bom/pom.xml | 6 + 17 files changed, 1314 insertions(+) create mode 100644 agents/semantickernel-agents-core/pom.xml create mode 100644 agents/semantickernel-agents-core/src/main/java/com/microsoft/semantickernel/agents/chatcompletion/ChatCompletionAgent.java create mode 100644 agents/semantickernel-agents-core/src/main/java/com/microsoft/semantickernel/agents/chatcompletion/ChatHistoryAgentThread.java create mode 100644 agents/semantickernel-agents-openai/pom.xml create mode 100644 samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/plugins/github/GitHubModel.java create mode 100644 samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/plugins/github/GitHubPlugin.java create mode 100644 samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/agents/CompletionsAgent.java create mode 100644 semantickernel-api/src/main/java/com/microsoft/semantickernel/agents/Agent.java create mode 100644 semantickernel-api/src/main/java/com/microsoft/semantickernel/agents/AgentInvokeOptions.java create mode 100644 semantickernel-api/src/main/java/com/microsoft/semantickernel/agents/AgentResponseItem.java create mode 100644 semantickernel-api/src/main/java/com/microsoft/semantickernel/agents/AgentThread.java create mode 100644 semantickernel-api/src/main/java/com/microsoft/semantickernel/agents/BaseAgentThread.java create mode 100644 semantickernel-api/src/main/java/com/microsoft/semantickernel/agents/KernelAgent.java diff --git a/agents/semantickernel-agents-core/pom.xml b/agents/semantickernel-agents-core/pom.xml new file mode 100644 index 000000000..4eec1b768 --- /dev/null +++ b/agents/semantickernel-agents-core/pom.xml @@ -0,0 +1,25 @@ + + + 4.0.0 + + com.microsoft.semantic-kernel + semantickernel-parent + 1.4.4-SNAPSHOT + ../../pom.xml + + + semantickernel-agents-core + + Semantic Kernel Chat Completion Agent + Chat Completion Agent for Semantic Kernel + + + + com.microsoft.semantic-kernel + semantickernel-api + + + + \ No newline at end of file diff --git a/agents/semantickernel-agents-core/src/main/java/com/microsoft/semantickernel/agents/chatcompletion/ChatCompletionAgent.java b/agents/semantickernel-agents-core/src/main/java/com/microsoft/semantickernel/agents/chatcompletion/ChatCompletionAgent.java new file mode 100644 index 000000000..c8c29007f --- /dev/null +++ b/agents/semantickernel-agents-core/src/main/java/com/microsoft/semantickernel/agents/chatcompletion/ChatCompletionAgent.java @@ -0,0 +1,238 @@ +package com.microsoft.semantickernel.agents.chatcompletion; + +import com.microsoft.semantickernel.Kernel; +import com.microsoft.semantickernel.agents.AgentInvokeOptions; +import com.microsoft.semantickernel.agents.AgentResponseItem; +import com.microsoft.semantickernel.agents.AgentThread; +import com.microsoft.semantickernel.agents.KernelAgent; +import com.microsoft.semantickernel.builders.SemanticKernelBuilder; +import com.microsoft.semantickernel.contextvariables.ContextVariable; +import com.microsoft.semantickernel.orchestration.InvocationContext; +import com.microsoft.semantickernel.orchestration.InvocationReturnMode; +import com.microsoft.semantickernel.orchestration.PromptExecutionSettings; +import com.microsoft.semantickernel.orchestration.ToolCallBehavior; +import com.microsoft.semantickernel.semanticfunctions.KernelArguments; +import com.microsoft.semantickernel.semanticfunctions.PromptTemplate; +import com.microsoft.semantickernel.semanticfunctions.PromptTemplateConfig; +import com.microsoft.semantickernel.semanticfunctions.PromptTemplateFactory; +import com.microsoft.semantickernel.services.ServiceNotFoundException; +import com.microsoft.semantickernel.services.chatcompletion.AuthorRole; +import com.microsoft.semantickernel.services.chatcompletion.ChatCompletionService; +import com.microsoft.semantickernel.services.chatcompletion.ChatHistory; +import com.microsoft.semantickernel.services.chatcompletion.ChatMessageContent; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +public class ChatCompletionAgent extends KernelAgent { + + ChatCompletionAgent( + String id, + String name, + String description, + Kernel kernel, + KernelArguments kernelArguments, + InvocationContext context, + String instructions, + PromptTemplate template + ) { + super( + id, + name, + description, + kernel, + kernelArguments, + context, + instructions, + template + ); + } + + /** + * Invoke the agent with the given chat history. + * + * @param messages The chat history to process + * @param thread The agent thread to use + * @param options The options for invoking the agent + * @return A Mono containing the agent response + */ + @Override + public Mono>>> invokeAsync(List> messages, AgentThread thread, AgentInvokeOptions options) { + + Mono chatHistoryFromThread = this.ensureThreadExistsAsync(messages, thread, ChatHistoryAgentThread::new) + .cast(ChatHistoryAgentThread.class) + .map(ChatHistoryAgentThread::getChatHistory) + .flatMap(threadChatHistory -> { + return Mono.just(new ChatHistory(threadChatHistory.getMessages())); + }); + + + Mono>> updatedChatHistory = chatHistoryFromThread.flatMap( + chatHistory -> internalInvokeAsync( + this.getName(), + chatHistory, + options + ) + ); + + return updatedChatHistory.flatMap(chatMessageContents -> { + return Flux.fromIterable(chatMessageContents) + .concatMap(chatMessageContent -> this.notifyThreadOfNewMessageAsync(thread, chatMessageContent)) + .then(Mono.just(chatMessageContents)); // return the original list + }).flatMap(chatMessageContents -> { + return Mono.just(chatMessageContents.stream() + .map(chatMessageContent -> { + return new AgentResponseItem>( + chatMessageContent, + thread); + }).collect(Collectors.toList())); + }); + } + + private Mono>> internalInvokeAsync( + String agentName, + ChatHistory history, + AgentInvokeOptions options + ) { + final Kernel kernel = options.getKernel() != null ? options.getKernel() : this.kernel; + final KernelArguments arguments = mergeArguments(options.getKernelArguments()); + final String additionalInstructions = options.getAdditionalInstructions(); + final InvocationContext invocationContext = options.getInvocationContext() != null ? options.getInvocationContext() : this.invocationContext; + + try { + ChatCompletionService chatCompletionService = kernel.getService(ChatCompletionService.class, arguments); + + PromptExecutionSettings executionSettings = invocationContext.getPromptExecutionSettings() != null + ? invocationContext.getPromptExecutionSettings() + : kernelArguments.getExecutionSettings().get(chatCompletionService.getServiceId()); + + final InvocationContext newMessagesContext = InvocationContext.builder() + .withPromptExecutionSettings(executionSettings) + .withToolCallBehavior(invocationContext.getToolCallBehavior()) + .withTelemetry(invocationContext.getTelemetry()) + .withContextVariableConverter(invocationContext.getContextVariableTypes()) + .withKernelHooks(invocationContext.getKernelHooks()) + .withReturnMode(InvocationReturnMode.FULL_HISTORY) + .build(); + + return formatInstructionsAsync(kernel, arguments, newMessagesContext).flatMap( + instructions -> { + // Create a new chat history with the instructions + ChatHistory chat = new ChatHistory( + instructions + ); + + // Add agent additional instructions + if (additionalInstructions != null) { + chat.addMessage(new ChatMessageContent<>( + AuthorRole.SYSTEM, + additionalInstructions + )); + } + + chat.addAll(history); + int previousHistorySize = chat.getMessages().size(); + + return chatCompletionService.getChatMessageContentsAsync(chat, kernel, newMessagesContext) + .map(chatMessageContents -> { + return chatMessageContents.subList( + previousHistorySize, + chatMessageContents.size()); + }); + } + ); + + + } catch (ServiceNotFoundException e) { + throw new RuntimeException(e); + } + } + + /** + * Builder for creating instances of ChatCompletionAgent. + */ + public static Builder builder() { + return new Builder(); + } + + public static class Builder implements SemanticKernelBuilder { + private String id; + private String name; + private String description; + private Kernel kernel; + private KernelArguments KernelArguments; + private InvocationContext invocationContext; + private String instructions; + private PromptTemplate template; + + public Builder withId(String id) { + this.id = id; + return this; + } + + public Builder withName(String name) { + this.name = name; + return this; + } + + public Builder withDescription(String description) { + this.description = description; + return this; + } + + public Builder withKernel(Kernel kernel) { + this.kernel = kernel; + return this; + } + + public Builder withKernelArguments(KernelArguments KernelArguments) { + this.KernelArguments = KernelArguments; + return this; + } + + public Builder withInstructions(String instructions) { + this.instructions = instructions; + return this; + } + + public Builder withInvocationContext(InvocationContext invocationContext) { + this.invocationContext = invocationContext; + return this; + } + + public Builder withTemplate(PromptTemplate template) { + this.template = template; + return this; + } + + public ChatCompletionAgent build() { + return new ChatCompletionAgent( + id, + name, + description, + kernel, + KernelArguments, + invocationContext, + instructions, + template + ); + } + + public ChatCompletionAgent build(PromptTemplateConfig promptTemplateConfig, PromptTemplateFactory promptTemplateFactory) { + return new ChatCompletionAgent( + id, + name, + description, + kernel, + KernelArguments, + invocationContext, + promptTemplateConfig.getTemplate(), + promptTemplateFactory.tryCreate(promptTemplateConfig) + ); + } + } +} diff --git a/agents/semantickernel-agents-core/src/main/java/com/microsoft/semantickernel/agents/chatcompletion/ChatHistoryAgentThread.java b/agents/semantickernel-agents-core/src/main/java/com/microsoft/semantickernel/agents/chatcompletion/ChatHistoryAgentThread.java new file mode 100644 index 000000000..b000ca9f9 --- /dev/null +++ b/agents/semantickernel-agents-core/src/main/java/com/microsoft/semantickernel/agents/chatcompletion/ChatHistoryAgentThread.java @@ -0,0 +1,64 @@ +package com.microsoft.semantickernel.agents.chatcompletion; + +import com.microsoft.semantickernel.agents.AgentThread; +import com.microsoft.semantickernel.agents.BaseAgentThread; +import com.microsoft.semantickernel.services.chatcompletion.ChatHistory; +import com.microsoft.semantickernel.services.chatcompletion.ChatMessageContent; +import reactor.core.publisher.Mono; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.List; +import java.util.UUID; + +public class ChatHistoryAgentThread extends BaseAgentThread { + private ChatHistory chatHistory; + + public ChatHistoryAgentThread() { + } + + /** + * Constructor for com.microsoft.semantickernel.agents.chatcompletion.ChatHistoryAgentThread. + * + * @param id The ID of the thread. + * @param chatHistory The chat history. + */ + public ChatHistoryAgentThread(String id, @Nullable ChatHistory chatHistory) { + super(id); + this.chatHistory = chatHistory; + } + + /** + * Get the chat history. + * + * @return The chat history. + */ + public ChatHistory getChatHistory() { + return chatHistory; + } + + @Override + public Mono createAsync() { + if (this.id == null) { + this.id = UUID.randomUUID().toString(); + chatHistory = new ChatHistory(); + } + return Mono.just(id); + } + + @Override + public Mono deleteAsync() { + return Mono.fromRunnable(chatHistory::clear); + } + + @Override + public Mono onNewMessageAsync(ChatMessageContent newMessage) { + return Mono.fromRunnable(() -> { + chatHistory.addMessage(newMessage); + }); + } + + public List> getMessages() { + return chatHistory.getMessages(); + } +} diff --git a/agents/semantickernel-agents-openai/pom.xml b/agents/semantickernel-agents-openai/pom.xml new file mode 100644 index 000000000..815a0d5bf --- /dev/null +++ b/agents/semantickernel-agents-openai/pom.xml @@ -0,0 +1,26 @@ + + + 4.0.0 + + com.microsoft.semantic-kernel + semantickernel-parent + 1.4.4-SNAPSHOT + + + semantickernel-agents-openai + + + 17 + 17 + UTF-8 + + + + com.microsoft.semantic-kernel + semantickernel-api + + + + \ No newline at end of file diff --git a/pom.xml b/pom.xml index 30963f54b..f1536e875 100644 --- a/pom.xml +++ b/pom.xml @@ -77,6 +77,8 @@ data/semantickernel-data-azureaisearch data/semantickernel-data-jdbc data/semantickernel-data-redis + agents/semantickernel-agents-openai + agents/semantickernel-agents-core diff --git a/samples/semantickernel-concepts/semantickernel-syntax-examples/pom.xml b/samples/semantickernel-concepts/semantickernel-syntax-examples/pom.xml index 1f6bc1811..3ccc114c1 100644 --- a/samples/semantickernel-concepts/semantickernel-syntax-examples/pom.xml +++ b/samples/semantickernel-concepts/semantickernel-syntax-examples/pom.xml @@ -49,6 +49,11 @@ semantickernel-data-redis + + com.microsoft.semantic-kernel + semantickernel-agents-core + + com.microsoft.semantic-kernel semantickernel-experimental diff --git a/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/plugins/github/GitHubModel.java b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/plugins/github/GitHubModel.java new file mode 100644 index 000000000..180ec8ed2 --- /dev/null +++ b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/plugins/github/GitHubModel.java @@ -0,0 +1,219 @@ +package com.microsoft.semantickernel.samples.plugins.github; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; + +public abstract class GitHubModel { + public final static ObjectMapper objectMapper = new ObjectMapper() + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + + @Override + public String toString() { + try { + return objectMapper.writeValueAsString(this); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + } + + public static class User extends GitHubModel { + @JsonProperty("login") + private String login; + @JsonProperty("id") + private long id; + @JsonProperty("name") + private String name; + @JsonProperty("company") + private String company; + @JsonProperty("html_url") + private String url; + @JsonCreator + public User(@JsonProperty("login") String login, + @JsonProperty("id") long id, + @JsonProperty("name") String name, + @JsonProperty("company") String company, + @JsonProperty("html_url") String url) { + this.login = login; + this.id = id; + this.name = name; + this.company = company; + this.url = url; + } + + public String getLogin() { + return login; + } + public long getId() { + return id; + } + public String getName() { + return name; + } + public String getCompany() { + return company; + } + public String getUrl() { + return url; + } + } + + public static class Repository extends GitHubModel { + @JsonProperty("id") + private long id; + @JsonProperty("full_name") + private String name; + @JsonProperty("description") + private String description; + @JsonProperty("html_url") + private String url; + @JsonCreator + public Repository(@JsonProperty("id") long id, + @JsonProperty("full_name") String name, + @JsonProperty("description") String description, + @JsonProperty("html_url") String url) { + this.id = id; + this.name = name; + this.description = description; + this.url = url; + } + + public long getId() { + return id; + } + public String getName() { + return name; + } + public String getDescription() { + return description; + } + public String getUrl() { + return url; + } + + @Override + public String toString() { + try { + return objectMapper.writeValueAsString(this); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + } + } + + public static class Issue extends GitHubModel { + @JsonProperty("id") + private long id; + @JsonProperty("number") + private long number; + @JsonProperty("title") + private String title; + @JsonProperty("state") + private String state; + @JsonProperty("html_url") + private String url; + @JsonProperty("labels") + private Label[] labels; + @JsonProperty("created_at") + private String createdAt; + @JsonProperty("closed_at") + private String closedAt; + + @JsonCreator + public Issue(@JsonProperty("id") long id, + @JsonProperty("number") long number, + @JsonProperty("title") String title, + @JsonProperty("state") String state, + @JsonProperty("html_url") String url, + @JsonProperty("labels") Label[] labels, + @JsonProperty("created_at") String createdAt, + @JsonProperty("closed_at") String closedAt) { + this.id = id; + this.number = number; + this.title = title; + this.state = state; + this.url = url; + this.labels = labels; + this.createdAt = createdAt; + this.closedAt = closedAt; + } + + public long getId() { + return id; + } + public long getNumber() { + return number; + } + public String getTitle() { + return title; + } + public String getState() { + return state; + } + public String getUrl() { + return url; + } + public Label[] getLabels() { + return labels; + } + public String getCreatedAt() { + return createdAt; + } + public String getClosedAt() { + return closedAt; + } + } + + public static class IssueDetail extends Issue { + @JsonProperty("body") + private String body; + + @JsonCreator + public IssueDetail(@JsonProperty("id") long id, + @JsonProperty("number") long number, + @JsonProperty("title") String title, + @JsonProperty("state") String state, + @JsonProperty("html_url") String url, + @JsonProperty("labels") Label[] labels, + @JsonProperty("created_at") String createdAt, + @JsonProperty("closed_at") String closedAt, + @JsonProperty("body") String body) { + super(id, number, title, state, url, labels, createdAt, closedAt); + this.body = body; + } + + public String getBody() { + return body; + } + } + + public static class Label extends GitHubModel { + @JsonProperty("id") + private long id; + @JsonProperty("name") + private String name; + @JsonProperty("description") + private String description; + + @JsonCreator + public Label(@JsonProperty("id") long id, + @JsonProperty("name") String name, + @JsonProperty("description") String description) { + this.id = id; + this.name = name; + this.description = description; + } + + public long getId() { + return id; + } + public String getName() { + return name; + } + public String getDescription() { + return description; + } + } +} diff --git a/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/plugins/github/GitHubPlugin.java b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/plugins/github/GitHubPlugin.java new file mode 100644 index 000000000..2d1fb4446 --- /dev/null +++ b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/plugins/github/GitHubPlugin.java @@ -0,0 +1,165 @@ +package com.microsoft.semantickernel.samples.plugins.github; + +import reactor.core.publisher.Mono; +import reactor.netty.http.client.HttpClient; +import com.microsoft.semantickernel.semanticfunctions.annotations.DefineKernelFunction; +import com.microsoft.semantickernel.semanticfunctions.annotations.KernelFunctionParameter; + +import java.io.IOException; +import java.util.List; + +public class GitHubPlugin { + public static final String baseUrl = "https://api.github.com"; + private final String token; + + public GitHubPlugin(String token) { + this.token = token; + } + + @DefineKernelFunction(name = "get_user_info", description = "Get user information from GitHub", + returnType = "com.microsoft.semantickernel.samples.plugins.github.GitHubModel$User") + public Mono getUserProfileAsync() { + HttpClient client = createClient(); + + return makeRequestAsync(client, "/user") + .map(json -> { + try { + return GitHubModel.objectMapper.readValue(json, GitHubModel.User.class); + } catch (IOException e) { + throw new IllegalStateException("Failed to deserialize GitHubUser", e); + } + }); + } + + @DefineKernelFunction(name = "get_repo_info", description = "Get repository information from GitHub", + returnType = "com.microsoft.semantickernel.samples.plugins.github.GitHubModel$Repository") + public Mono getRepositoryAsync( + @KernelFunctionParameter( + name = "organization", + description = "The name of the repository to retrieve information for" + ) String organization, + @KernelFunctionParameter( + name = "repo_name", + description = "The name of the repository to retrieve information for" + ) String repoName + ) { + HttpClient client = createClient(); + + return makeRequestAsync(client, String.format("/repos/%s/%s", organization, repoName)) + .map(json -> { + try { + return GitHubModel.objectMapper.readValue(json, GitHubModel.Repository.class); + } catch (IOException e) { + throw new IllegalStateException("Failed to deserialize GitHubRepository", e); + } + }); + } + + @DefineKernelFunction(name = "get_issues", description = "Get issues from GitHub", + returnType = "java.util.List") + public Mono> getIssuesAsync( + @KernelFunctionParameter( + name = "organization", + description = "The name of the organization to retrieve issues for" + ) String organization, + @KernelFunctionParameter( + name = "repo_name", + description = "The name of the repository to retrieve issues for" + ) String repoName, + @KernelFunctionParameter( + name = "max_results", + description = "The maximum number of issues to retrieve", + required = false, + defaultValue = "10", + type = int.class + ) int maxResults, + @KernelFunctionParameter( + name = "state", + description = "The state of the issues to retrieve", + required = false, + defaultValue = "open" + ) String state, + @KernelFunctionParameter( + name = "assignee", + description = "The assignee of the issues to retrieve", + required = false + ) String assignee + ) { + HttpClient client = createClient(); + + String query = String.format("/repos/%s/%s/issues", organization, repoName); + query = buildQueryString(query, "state", state); + query = buildQueryString(query, "assignee", assignee); + query = buildQueryString(query, "per_page", String.valueOf(maxResults)); + + return makeRequestAsync(client, query) + .flatMap(json -> { + try { + GitHubModel.Issue[] issues = GitHubModel.objectMapper.readValue(json, GitHubModel.Issue[].class); + return Mono.just(List.of(issues)); + } catch (IOException e) { + throw new IllegalStateException("Failed to deserialize GitHubIssues", e); + } + }); + } + + @DefineKernelFunction(name = "get_issue_detail_info", description = "Get issue detail information from GitHub", + returnType = "com.microsoft.semantickernel.samples.plugins.github.GitHubModel$IssueDetail") + public GitHubModel.IssueDetail getIssueDetailAsync( + @KernelFunctionParameter( + name = "organization", + description = "The name of the repository to retrieve information for" + ) String organization, + @KernelFunctionParameter( + name = "repo_name", + description = "The name of the repository to retrieve information for" + ) String repoName, + @KernelFunctionParameter( + name = "issue_number", + description = "The issue number to retrieve information for", + type = int.class + ) int issueNumber + ) { + HttpClient client = createClient(); + + return makeRequestAsync(client, String.format("/repos/%s/%s/issues/%d", organization, repoName, issueNumber)) + .map(json -> { + try { + return GitHubModel.objectMapper.readValue(json, GitHubModel.IssueDetail.class); + } catch (IOException e) { + throw new IllegalStateException("Failed to deserialize GitHubIssue", e); + } + }).block(); + } + + private HttpClient createClient() { + return HttpClient.create() + .baseUrl(baseUrl) + .headers(headers -> { + headers.add("User-Agent", "request"); + headers.add("Accept", "application/vnd.github+json"); + headers.add("Authorization", "Bearer " + token); + headers.add("X-GitHub-Api-Version", "2022-11-28"); + }); + } + + private static String buildQueryString(String path, String param, String value) { + if (value == null || value.isEmpty() || value.equals(KernelFunctionParameter.NO_DEFAULT_VALUE)) { + return path; + } + + return path + (path.contains("?") ? "&" : "?") + param + "=" + value; + } + + private Mono makeRequestAsync(HttpClient client, String path) { + return client + .get() + .uri(path) + .responseSingle((res, content) -> { + if (res.status().code() != 200) { + return Mono.error(new IllegalStateException("Request failed: " + res.status())); + } + return content.asString(); + }); + } +} diff --git a/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/agents/CompletionsAgent.java b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/agents/CompletionsAgent.java new file mode 100644 index 000000000..38e9f1e81 --- /dev/null +++ b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/agents/CompletionsAgent.java @@ -0,0 +1,145 @@ +package com.microsoft.semantickernel.samples.syntaxexamples.agents; + +import com.azure.ai.openai.OpenAIAsyncClient; +import com.azure.ai.openai.OpenAIClientBuilder; +import com.azure.core.credential.AzureKeyCredential; +import com.azure.core.credential.KeyCredential; +import com.microsoft.semantickernel.Kernel; +import com.microsoft.semantickernel.agents.AgentInvokeOptions; +import com.microsoft.semantickernel.agents.chatcompletion.ChatCompletionAgent; +import com.microsoft.semantickernel.agents.chatcompletion.ChatHistoryAgentThread; +import com.microsoft.semantickernel.aiservices.openai.chatcompletion.OpenAIChatCompletion; +import com.microsoft.semantickernel.contextvariables.ContextVariableTypeConverter; +import com.microsoft.semantickernel.contextvariables.ContextVariableTypes; +import com.microsoft.semantickernel.implementation.templateengine.tokenizer.DefaultPromptTemplate; +import com.microsoft.semantickernel.orchestration.InvocationContext; +import com.microsoft.semantickernel.orchestration.PromptExecutionSettings; +import com.microsoft.semantickernel.orchestration.ToolCallBehavior; +import com.microsoft.semantickernel.plugin.KernelPluginFactory; +import com.microsoft.semantickernel.samples.plugins.github.GitHubModel; +import com.microsoft.semantickernel.samples.plugins.github.GitHubPlugin; +import com.microsoft.semantickernel.semanticfunctions.KernelArguments; +import com.microsoft.semantickernel.semanticfunctions.PromptTemplateConfig; +import com.microsoft.semantickernel.services.chatcompletion.AuthorRole; +import com.microsoft.semantickernel.services.chatcompletion.ChatCompletionService; +import com.microsoft.semantickernel.services.chatcompletion.ChatMessageContent; + +import java.util.List; +import java.util.Scanner; + +public class CompletionsAgent { + private static final String CLIENT_KEY = System.getenv("CLIENT_KEY"); + private static final String AZURE_CLIENT_KEY = System.getenv("AZURE_CLIENT_KEY"); + + // Only required if AZURE_CLIENT_KEY is set + private static final String CLIENT_ENDPOINT = System.getenv("CLIENT_ENDPOINT"); + private static final String MODEL_ID = System.getenv() + .getOrDefault("MODEL_ID", "gpt-35-turbo"); + + private static final String GITHUB_PAT = System.getenv("GITHUB_PAT"); + public static void main(String[] args) { + System.out.println("======== ChatCompletion Agent ========"); + + OpenAIAsyncClient client; + + if (AZURE_CLIENT_KEY != null) { + client = new OpenAIClientBuilder() + .credential(new AzureKeyCredential(AZURE_CLIENT_KEY)) + .endpoint(CLIENT_ENDPOINT) + .buildAsyncClient(); + + } else { + client = new OpenAIClientBuilder() + .credential(new KeyCredential(CLIENT_KEY)) + .buildAsyncClient(); + } + + ChatCompletionService chatCompletion = OpenAIChatCompletion.builder() + .withModelId(MODEL_ID) + .withOpenAIAsyncClient(client) + .build(); + + System.out.println("------------------------"); + + ContextVariableTypes.addGlobalConverter( + new ContextVariableTypeConverter<>( + GitHubModel.Issue.class, + o -> (GitHubModel.Issue) o, + o -> o.toString(), + s -> null + ) + ); + + Kernel kernel = Kernel.builder() + .withAIService(ChatCompletionService.class, chatCompletion) + .withPlugin(KernelPluginFactory.createFromObject(new GitHubPlugin(GITHUB_PAT), + "GitHubPlugin")) + .build(); + + InvocationContext invocationContext = InvocationContext.builder() + .withToolCallBehavior(ToolCallBehavior.allowAllKernelFunctions(true)) + .build(); + + ChatCompletionAgent agent = ChatCompletionAgent.builder() + .withKernel(kernel) + .withKernelArguments( + KernelArguments.builder() + .withVariable("repository", "microsoft/semantic-kernel-java") + .withExecutionSettings(PromptExecutionSettings.builder() + .build()) + .build() + ) + .withInvocationContext(invocationContext) + .withTemplate( + DefaultPromptTemplate.build( + PromptTemplateConfig.builder() + .withTemplate( + """ + You are an agent designed to query and retrieve information from a single GitHub repository in a read-only manner. + You are also able to access the profile of the active user. + + Use the current date and time to provide up-to-date details or time-sensitive responses. + + The repository you are querying is a public repository with the following name: {{$repository}} + + The current date and time is: {{$now}}. + """ + ) + .build() + ) + ).build(); + + ChatHistoryAgentThread agentThread = new ChatHistoryAgentThread(); + agentThread.createAsync().block(); + + Scanner scanner = new Scanner(System.in); + + while (true) { + System.out.print("> "); + + String input = scanner.nextLine(); + if (input.equalsIgnoreCase("exit")) { + break; + } + + var message = new ChatMessageContent<>(AuthorRole.USER, input); + KernelArguments arguments = KernelArguments.builder() + .withVariable("now", System.currentTimeMillis()) + .build(); + + var responses = agent.invokeAsync( + List.of(message), + agentThread, + AgentInvokeOptions.builder() + .withKernel(kernel) + .withKernelArguments(arguments) + .build() + ).block(); + + for (var response : responses) { + System.out.println("> " + response.getMessage()); + } + } + } + +} diff --git a/semantickernel-api/src/main/java/com/microsoft/semantickernel/agents/Agent.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/agents/Agent.java new file mode 100644 index 000000000..314201e9a --- /dev/null +++ b/semantickernel-api/src/main/java/com/microsoft/semantickernel/agents/Agent.java @@ -0,0 +1,60 @@ +package com.microsoft.semantickernel.agents; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.function.Function; +import java.util.function.Supplier; + +import com.microsoft.semantickernel.Kernel; +import com.microsoft.semantickernel.orchestration.InvocationContext; +import com.microsoft.semantickernel.orchestration.PromptExecutionSettings; +import com.microsoft.semantickernel.semanticfunctions.KernelArguments; +import com.microsoft.semantickernel.semanticfunctions.PromptTemplate; +import com.microsoft.semantickernel.services.chatcompletion.ChatHistory; +import com.microsoft.semantickernel.services.chatcompletion.ChatMessageContent; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +/** + * Interface for a semantic kernel agent. + */ +public interface Agent { + + /** + * Gets the agent's ID. + * + * @return The agent's ID + */ + String getId(); + + /** + * Gets the agent's name. + * + * @return The agent's name + */ + String getName(); + + /** + * Gets the agent's description. + * + * @return The agent's description + */ + String getDescription(); + + /** + * Invoke the agent with the given chat history. + * + * @param messages The chat history to process + * @param thread The agent thread to use + * @param options The options for invoking the agent + * @return A Mono containing the agent response + */ + Mono>>> invokeAsync(List> messages, AgentThread thread, AgentInvokeOptions options); + + + + + Mono notifyThreadOfNewMessageAsync(AgentThread thread, ChatMessageContent newMessage); +} \ No newline at end of file diff --git a/semantickernel-api/src/main/java/com/microsoft/semantickernel/agents/AgentInvokeOptions.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/agents/AgentInvokeOptions.java new file mode 100644 index 000000000..a542c40a5 --- /dev/null +++ b/semantickernel-api/src/main/java/com/microsoft/semantickernel/agents/AgentInvokeOptions.java @@ -0,0 +1,126 @@ +package com.microsoft.semantickernel.agents; + +import com.microsoft.semantickernel.Kernel; +import com.microsoft.semantickernel.builders.SemanticKernelBuilder; +import com.microsoft.semantickernel.orchestration.InvocationContext; +import com.microsoft.semantickernel.semanticfunctions.KernelArguments; + +/** + * Options for invoking an agent. + */ +public class AgentInvokeOptions { + + private final KernelArguments KernelArguments; + private final Kernel kernel; + private final String additionalInstructions; + private final InvocationContext invocationContext; + + + /** + * Constructor for AgentInvokeOptions. + * + * @param KernelArguments The arguments for the kernel function. + * @param kernel The kernel to use. + * @param additionalInstructions Additional instructions for the agent. + * @param invocationContext The invocation context. + */ + public AgentInvokeOptions(KernelArguments KernelArguments, Kernel kernel, String additionalInstructions, + InvocationContext invocationContext) { + this.KernelArguments = KernelArguments; + this.kernel = kernel; + this.additionalInstructions = additionalInstructions; + this.invocationContext = invocationContext; + } + + public KernelArguments getKernelArguments() { + return KernelArguments; + } + + public Kernel getKernel() { + return kernel; + } + + public String getAdditionalInstructions() { + return additionalInstructions; + } + + public InvocationContext getInvocationContext() { + return invocationContext; + } + + + + /** + * Builder for AgentInvokeOptions. + */ + public static Builder builder() { + return new Builder(); + } + + public static class Builder implements SemanticKernelBuilder { + + private KernelArguments kernelArguments; + private Kernel kernel; + private String additionalInstructions; + private InvocationContext invocationContext; + + /** + * Set the kernel arguments. + * + * @param kernelArguments The kernel arguments. + * @return The builder. + */ + public Builder withKernelArguments(KernelArguments kernelArguments) { + this.kernelArguments = kernelArguments; + return this; + } + + /** + * Set the kernel. + * + * @param kernel The kernel. + * @return The builder. + */ + public Builder withKernel(Kernel kernel) { + this.kernel = kernel; + return this; + } + + /** + * Set additional instructions. + * + * @param additionalInstructions The additional instructions. + * @return The builder. + */ + public Builder withAdditionalInstructions(String additionalInstructions) { + this.additionalInstructions = additionalInstructions; + return this; + } + + /** + * Set the invocation context. + * + * @param invocationContext The invocation context. + * @return The builder. + */ + public Builder withInvocationContext(InvocationContext invocationContext) { + this.invocationContext = invocationContext; + return this; + } + + /** + * Build the object. + * + * @return a constructed object. + */ + @Override + public AgentInvokeOptions build() { + return new AgentInvokeOptions( + kernelArguments != null ? kernelArguments : com.microsoft.semantickernel.semanticfunctions.KernelArguments.builder().build(), + kernel != null ? kernel : Kernel.builder().build(), + additionalInstructions, + invocationContext + ); + } + } +} \ No newline at end of file diff --git a/semantickernel-api/src/main/java/com/microsoft/semantickernel/agents/AgentResponseItem.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/agents/AgentResponseItem.java new file mode 100644 index 000000000..61ffdd6d0 --- /dev/null +++ b/semantickernel-api/src/main/java/com/microsoft/semantickernel/agents/AgentResponseItem.java @@ -0,0 +1,19 @@ +package com.microsoft.semantickernel.agents; + +public class AgentResponseItem { + private final T message; + private final AgentThread thread; + + public AgentResponseItem(T message, AgentThread thread) { + this.message = message; + this.thread = thread; + } + + public T getMessage() { + return message; + } + + public AgentThread getThread() { + return thread; + } +} diff --git a/semantickernel-api/src/main/java/com/microsoft/semantickernel/agents/AgentThread.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/agents/AgentThread.java new file mode 100644 index 000000000..0c83751a9 --- /dev/null +++ b/semantickernel-api/src/main/java/com/microsoft/semantickernel/agents/AgentThread.java @@ -0,0 +1,45 @@ +package com.microsoft.semantickernel.agents; + +import com.microsoft.semantickernel.services.chatcompletion.ChatMessageContent; +import reactor.core.publisher.Mono; + +/** + * Interface for an agent thread. + */ +public interface AgentThread { + /** + * Get the thread ID. + * + * @return The thread ID. + */ + String getId(); + + /** + * Create a new thread. + * + * @return A Mono containing the thread ID. + */ + Mono createAsync(); + + /** + * Delete the thread. + * + * @return A Mono indicating completion. + */ + Mono deleteAsync(); + + /** + * Check if the thread is deleted. + * + * @return A Mono containing true if the thread is deleted, false otherwise. + */ + boolean isDeleted(); + + /** + * Handle a new message in the thread. + * + * @param newMessage The new message to handle. + * @return A Mono indicating completion. + */ + Mono onNewMessageAsync(ChatMessageContent newMessage); +} \ No newline at end of file diff --git a/semantickernel-api/src/main/java/com/microsoft/semantickernel/agents/BaseAgentThread.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/agents/BaseAgentThread.java new file mode 100644 index 000000000..b7c97eea9 --- /dev/null +++ b/semantickernel-api/src/main/java/com/microsoft/semantickernel/agents/BaseAgentThread.java @@ -0,0 +1,23 @@ +package com.microsoft.semantickernel.agents; + +public abstract class BaseAgentThread implements AgentThread { + + protected String id; + protected boolean isDeleted; + + public BaseAgentThread() { + } + + public BaseAgentThread(String id) { + this.id = id; + } + + @Override + public String getId() { + return id; + } + @Override + public boolean isDeleted() { + return isDeleted; + } +} diff --git a/semantickernel-api/src/main/java/com/microsoft/semantickernel/agents/KernelAgent.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/agents/KernelAgent.java new file mode 100644 index 000000000..8aaa432ef --- /dev/null +++ b/semantickernel-api/src/main/java/com/microsoft/semantickernel/agents/KernelAgent.java @@ -0,0 +1,139 @@ +package com.microsoft.semantickernel.agents; + +import com.microsoft.semantickernel.Kernel; +import com.microsoft.semantickernel.orchestration.InvocationContext; +import com.microsoft.semantickernel.orchestration.PromptExecutionSettings; +import com.microsoft.semantickernel.semanticfunctions.KernelArguments; +import com.microsoft.semantickernel.semanticfunctions.PromptTemplate; +import com.microsoft.semantickernel.services.chatcompletion.ChatMessageContent; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.function.Supplier; + +public abstract class KernelAgent implements Agent { + + protected final String id; + + protected final String name; + + protected final String description; + + protected final Kernel kernel; + protected final KernelArguments kernelArguments; + protected final InvocationContext invocationContext; + protected final String instructions; + + protected final PromptTemplate template; + + protected KernelAgent( + String id, + String name, + String description, + Kernel kernel, + KernelArguments kernelArguments, + InvocationContext invocationContext, + String instructions, + PromptTemplate template + ) { + this.id = id != null ? id : UUID.randomUUID().toString(); + this.name = name; + this.description = description; + this.kernel = kernel; + this.kernelArguments = kernelArguments; + this.invocationContext = invocationContext; + this.instructions = instructions; + this.template = template; + } + + public String getId() { + return id; + } + + public String getName() { + return name; + } + + public String getDescription() { + return description; + } + + public Kernel getKernel() { + return kernel; + } + + public KernelArguments getKernelArguments() { + return kernelArguments; + } + + public String getInstructions() { + return instructions; + } + + public PromptTemplate getTemplate() { + return template; + } + + protected Mono ensureThreadExistsAsync(List> messages, AgentThread thread, Supplier constructor) { + return Mono.defer(() -> { + T newThread = thread != null ? (T) thread : constructor.get(); + + return newThread.createAsync() + .thenMany(Flux.fromIterable(messages)) + .concatMap(message -> { + return notifyThreadOfNewMessageAsync(newThread, message) + .then(Mono.just(message)); + }) + .then(Mono.just(newThread)); + }); + } + + @Override + public Mono notifyThreadOfNewMessageAsync(AgentThread thread, ChatMessageContent message) { + return Mono.defer(() -> { + return thread.onNewMessageAsync(message); + }); + } + + + /** + * Merges the provided arguments with the current arguments. + * Provided arguments will override the current arguments. + * + * @param arguments The arguments to merge with the current arguments. + */ + protected KernelArguments mergeArguments(KernelArguments arguments) { + if (arguments == null) { + return kernelArguments; + } + + Map executionSettings = new HashMap<>(kernelArguments.getExecutionSettings()); + executionSettings.putAll(arguments.getExecutionSettings()); + + return KernelArguments.builder() + .withVariables(kernelArguments) + .withVariables(arguments) + .withExecutionSettings(executionSettings) + .build(); + } + + /** + * Formats the instructions using the provided kernel, arguments, and context. + * + * @param kernel The kernel to use for formatting. + * @param arguments The arguments to use for formatting. + * @param context The context to use for formatting. + * @return A Mono that resolves to the formatted instructions. + */ + protected Mono formatInstructionsAsync(Kernel kernel, KernelArguments arguments, InvocationContext context) { + if (template != null) { + return template.renderAsync(kernel, arguments, context); + } else { + return Mono.just(instructions); + } + } +} diff --git a/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/chatcompletion/ChatHistory.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/chatcompletion/ChatHistory.java index 25cd8ea89..df5f18322 100644 --- a/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/chatcompletion/ChatHistory.java +++ b/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/chatcompletion/ChatHistory.java @@ -187,6 +187,13 @@ public ChatHistory addSystemMessage(String content) { return addMessage(AuthorRole.SYSTEM, content); } + /** + * Clear the chat history + */ + public void clear() { + chatMessageContents.clear(); + } + /** * Add all messages to the chat history * @param messages The messages to add to the chat history diff --git a/semantickernel-bom/pom.xml b/semantickernel-bom/pom.xml index bb4d766fd..32ca102b1 100644 --- a/semantickernel-bom/pom.xml +++ b/semantickernel-bom/pom.xml @@ -112,6 +112,12 @@ ${project.version} + + com.microsoft.semantic-kernel + semantickernel-agents-core + ${project.version} + + com.azure azure-ai-openai From e242d99f517383055184609488dedf4c3cbb9c1b Mon Sep 17 00:00:00 2001 From: Milder Hernandez Cagua Date: Mon, 21 Apr 2025 11:00:31 -0700 Subject: [PATCH 02/12] Updates --- .../agents/chatcompletion/ChatCompletionAgent.java | 6 +++--- .../samples/plugins/github/GitHubPlugin.java | 2 +- .../syntaxexamples/agents/CompletionsAgent.java | 11 ++++------- .../com/microsoft/semantickernel/agents/Agent.java | 8 +++++--- .../microsoft/semantickernel/agents/KernelAgent.java | 8 +++----- 5 files changed, 16 insertions(+), 19 deletions(-) diff --git a/agents/semantickernel-agents-core/src/main/java/com/microsoft/semantickernel/agents/chatcompletion/ChatCompletionAgent.java b/agents/semantickernel-agents-core/src/main/java/com/microsoft/semantickernel/agents/chatcompletion/ChatCompletionAgent.java index c8c29007f..413abbc2b 100644 --- a/agents/semantickernel-agents-core/src/main/java/com/microsoft/semantickernel/agents/chatcompletion/ChatCompletionAgent.java +++ b/agents/semantickernel-agents-core/src/main/java/com/microsoft/semantickernel/agents/chatcompletion/ChatCompletionAgent.java @@ -110,7 +110,7 @@ private Mono>> internalInvokeAsync( ? invocationContext.getPromptExecutionSettings() : kernelArguments.getExecutionSettings().get(chatCompletionService.getServiceId()); - final InvocationContext newMessagesContext = InvocationContext.builder() + final InvocationContext updatedInvocationContext = InvocationContext.builder() .withPromptExecutionSettings(executionSettings) .withToolCallBehavior(invocationContext.getToolCallBehavior()) .withTelemetry(invocationContext.getTelemetry()) @@ -119,7 +119,7 @@ private Mono>> internalInvokeAsync( .withReturnMode(InvocationReturnMode.FULL_HISTORY) .build(); - return formatInstructionsAsync(kernel, arguments, newMessagesContext).flatMap( + return formatInstructionsAsync(kernel, arguments, updatedInvocationContext).flatMap( instructions -> { // Create a new chat history with the instructions ChatHistory chat = new ChatHistory( @@ -137,7 +137,7 @@ private Mono>> internalInvokeAsync( chat.addAll(history); int previousHistorySize = chat.getMessages().size(); - return chatCompletionService.getChatMessageContentsAsync(chat, kernel, newMessagesContext) + return chatCompletionService.getChatMessageContentsAsync(chat, kernel, updatedInvocationContext) .map(chatMessageContents -> { return chatMessageContents.subList( previousHistorySize, diff --git a/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/plugins/github/GitHubPlugin.java b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/plugins/github/GitHubPlugin.java index 2d1fb4446..d3c59a152 100644 --- a/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/plugins/github/GitHubPlugin.java +++ b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/plugins/github/GitHubPlugin.java @@ -103,7 +103,7 @@ public Mono> getIssuesAsync( }); } - @DefineKernelFunction(name = "get_issue_detail_info", description = "Get issue detail information from GitHub", + @DefineKernelFunction(name = "get_issue_detail_info", description = "Get detail information of a single issue from GitHub", returnType = "com.microsoft.semantickernel.samples.plugins.github.GitHubModel$IssueDetail") public GitHubModel.IssueDetail getIssueDetailAsync( @KernelFunctionParameter( diff --git a/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/agents/CompletionsAgent.java b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/agents/CompletionsAgent.java index 38e9f1e81..9e2d82c70 100644 --- a/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/agents/CompletionsAgent.java +++ b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/agents/CompletionsAgent.java @@ -34,7 +34,7 @@ public class CompletionsAgent { // Only required if AZURE_CLIENT_KEY is set private static final String CLIENT_ENDPOINT = System.getenv("CLIENT_ENDPOINT"); private static final String MODEL_ID = System.getenv() - .getOrDefault("MODEL_ID", "gpt-35-turbo"); + .getOrDefault("MODEL_ID", "gpt-4o"); private static final String GITHUB_PAT = System.getenv("GITHUB_PAT"); public static void main(String[] args) { @@ -77,7 +77,7 @@ public static void main(String[] args) { .build(); InvocationContext invocationContext = InvocationContext.builder() - .withToolCallBehavior(ToolCallBehavior.allowAllKernelFunctions(true)) + .withToolCallBehavior(ToolCallBehavior.allowAllKernelFunctions(false)) .build(); ChatCompletionAgent agent = ChatCompletionAgent.builder() @@ -127,7 +127,7 @@ public static void main(String[] args) { .withVariable("now", System.currentTimeMillis()) .build(); - var responses = agent.invokeAsync( + var response = agent.invokeAsync( List.of(message), agentThread, AgentInvokeOptions.builder() @@ -136,10 +136,7 @@ public static void main(String[] args) { .build() ).block(); - for (var response : responses) { - System.out.println("> " + response.getMessage()); - } + System.out.println("> " + response.get(response.size() - 1).getMessage()); } } - } diff --git a/semantickernel-api/src/main/java/com/microsoft/semantickernel/agents/Agent.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/agents/Agent.java index 314201e9a..3a82550c6 100644 --- a/semantickernel-api/src/main/java/com/microsoft/semantickernel/agents/Agent.java +++ b/semantickernel-api/src/main/java/com/microsoft/semantickernel/agents/Agent.java @@ -53,8 +53,10 @@ public interface Agent { */ Mono>>> invokeAsync(List> messages, AgentThread thread, AgentInvokeOptions options); - - - + /** + * Notifies the agent of a new message. + * + * @param thread The agent thread to use + */ Mono notifyThreadOfNewMessageAsync(AgentThread thread, ChatMessageContent newMessage); } \ No newline at end of file diff --git a/semantickernel-api/src/main/java/com/microsoft/semantickernel/agents/KernelAgent.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/agents/KernelAgent.java index 8aaa432ef..a9af6f87e 100644 --- a/semantickernel-api/src/main/java/com/microsoft/semantickernel/agents/KernelAgent.java +++ b/semantickernel-api/src/main/java/com/microsoft/semantickernel/agents/KernelAgent.java @@ -2,6 +2,7 @@ import com.microsoft.semantickernel.Kernel; import com.microsoft.semantickernel.orchestration.InvocationContext; +import com.microsoft.semantickernel.orchestration.InvocationReturnMode; import com.microsoft.semantickernel.orchestration.PromptExecutionSettings; import com.microsoft.semantickernel.semanticfunctions.KernelArguments; import com.microsoft.semantickernel.semanticfunctions.PromptTemplate; @@ -18,16 +19,12 @@ public abstract class KernelAgent implements Agent { protected final String id; - protected final String name; - protected final String description; - protected final Kernel kernel; protected final KernelArguments kernelArguments; protected final InvocationContext invocationContext; protected final String instructions; - protected final PromptTemplate template; protected KernelAgent( @@ -45,7 +42,8 @@ protected KernelAgent( this.description = description; this.kernel = kernel; this.kernelArguments = kernelArguments; - this.invocationContext = invocationContext; + this.invocationContext = invocationContext != null + ? invocationContext : InvocationContext.builder().withReturnMode(InvocationReturnMode.FULL_HISTORY).build(); this.instructions = instructions; this.template = template; } From 83daec2a7de3a75b1f06f2f59489daab2dd56054 Mon Sep 17 00:00:00 2001 From: Milder Hernandez Cagua Date: Mon, 21 Apr 2025 22:30:01 -0700 Subject: [PATCH 03/12] Renaming sample and adjust invocation context --- .../chatcompletion/ChatCompletionAgent.java | 32 ++++++++++++------- ...letionsAgent.java => CompletionAgent.java} | 4 +-- 2 files changed, 23 insertions(+), 13 deletions(-) rename samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/agents/{CompletionsAgent.java => CompletionAgent.java} (99%) diff --git a/agents/semantickernel-agents-core/src/main/java/com/microsoft/semantickernel/agents/chatcompletion/ChatCompletionAgent.java b/agents/semantickernel-agents-core/src/main/java/com/microsoft/semantickernel/agents/chatcompletion/ChatCompletionAgent.java index 413abbc2b..b521ce59d 100644 --- a/agents/semantickernel-agents-core/src/main/java/com/microsoft/semantickernel/agents/chatcompletion/ChatCompletionAgent.java +++ b/agents/semantickernel-agents-core/src/main/java/com/microsoft/semantickernel/agents/chatcompletion/ChatCompletionAgent.java @@ -106,20 +106,30 @@ private Mono>> internalInvokeAsync( try { ChatCompletionService chatCompletionService = kernel.getService(ChatCompletionService.class, arguments); - PromptExecutionSettings executionSettings = invocationContext.getPromptExecutionSettings() != null + PromptExecutionSettings executionSettings = invocationContext != null && invocationContext.getPromptExecutionSettings() != null ? invocationContext.getPromptExecutionSettings() : kernelArguments.getExecutionSettings().get(chatCompletionService.getServiceId()); - final InvocationContext updatedInvocationContext = InvocationContext.builder() + ToolCallBehavior toolCallBehavior = invocationContext != null + ? invocationContext.getToolCallBehavior() + : ToolCallBehavior.allowAllKernelFunctions(false); + + // Build base invocation context + InvocationContext.Builder builder = InvocationContext.builder() .withPromptExecutionSettings(executionSettings) - .withToolCallBehavior(invocationContext.getToolCallBehavior()) - .withTelemetry(invocationContext.getTelemetry()) - .withContextVariableConverter(invocationContext.getContextVariableTypes()) - .withKernelHooks(invocationContext.getKernelHooks()) - .withReturnMode(InvocationReturnMode.FULL_HISTORY) - .build(); - - return formatInstructionsAsync(kernel, arguments, updatedInvocationContext).flatMap( + .withToolCallBehavior(toolCallBehavior) + .withReturnMode(InvocationReturnMode.FULL_HISTORY); + + if (invocationContext != null) { + builder = builder + .withTelemetry(invocationContext.getTelemetry()) + .withContextVariableConverter(invocationContext.getContextVariableTypes()) + .withKernelHooks(invocationContext.getKernelHooks()); + } + + InvocationContext agentInvocationContext = builder.build(); + + return formatInstructionsAsync(kernel, arguments, agentInvocationContext).flatMap( instructions -> { // Create a new chat history with the instructions ChatHistory chat = new ChatHistory( @@ -137,7 +147,7 @@ private Mono>> internalInvokeAsync( chat.addAll(history); int previousHistorySize = chat.getMessages().size(); - return chatCompletionService.getChatMessageContentsAsync(chat, kernel, updatedInvocationContext) + return chatCompletionService.getChatMessageContentsAsync(chat, kernel, agentInvocationContext) .map(chatMessageContents -> { return chatMessageContents.subList( previousHistorySize, diff --git a/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/agents/CompletionsAgent.java b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/agents/CompletionAgent.java similarity index 99% rename from samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/agents/CompletionsAgent.java rename to samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/agents/CompletionAgent.java index 9e2d82c70..180b0c804 100644 --- a/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/agents/CompletionsAgent.java +++ b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/agents/CompletionAgent.java @@ -27,7 +27,7 @@ import java.util.List; import java.util.Scanner; -public class CompletionsAgent { +public class CompletionAgent { private static final String CLIENT_KEY = System.getenv("CLIENT_KEY"); private static final String AZURE_CLIENT_KEY = System.getenv("AZURE_CLIENT_KEY"); @@ -77,7 +77,7 @@ public static void main(String[] args) { .build(); InvocationContext invocationContext = InvocationContext.builder() - .withToolCallBehavior(ToolCallBehavior.allowAllKernelFunctions(false)) + .withToolCallBehavior(ToolCallBehavior.allowAllKernelFunctions(true)) .build(); ChatCompletionAgent agent = ChatCompletionAgent.builder() From 4664b2b835cf364b305c880ca4fe0ab752142b2c Mon Sep 17 00:00:00 2001 From: Milder Hernandez Cagua Date: Thu, 24 Apr 2025 22:35:58 -0700 Subject: [PATCH 04/12] Updates --- .../chatcompletion/ChatCompletionAgent.java | 160 ++++++++++++------ .../ChatHistoryAgentThread.java | 51 +++++- .../agents/CompletionAgent.java | 26 ++- .../agents/AgentInvokeOptions.java | 20 ++- .../semantickernel/agents/AgentThread.java | 7 + .../semantickernel/agents/KernelAgent.java | 74 +++++--- .../chatcompletion/ChatMessageContent.java | 44 +++++ 7 files changed, 289 insertions(+), 93 deletions(-) diff --git a/agents/semantickernel-agents-core/src/main/java/com/microsoft/semantickernel/agents/chatcompletion/ChatCompletionAgent.java b/agents/semantickernel-agents-core/src/main/java/com/microsoft/semantickernel/agents/chatcompletion/ChatCompletionAgent.java index b521ce59d..2e9a0d7df 100644 --- a/agents/semantickernel-agents-core/src/main/java/com/microsoft/semantickernel/agents/chatcompletion/ChatCompletionAgent.java +++ b/agents/semantickernel-agents-core/src/main/java/com/microsoft/semantickernel/agents/chatcompletion/ChatCompletionAgent.java @@ -6,7 +6,6 @@ import com.microsoft.semantickernel.agents.AgentThread; import com.microsoft.semantickernel.agents.KernelAgent; import com.microsoft.semantickernel.builders.SemanticKernelBuilder; -import com.microsoft.semantickernel.contextvariables.ContextVariable; import com.microsoft.semantickernel.orchestration.InvocationContext; import com.microsoft.semantickernel.orchestration.InvocationReturnMode; import com.microsoft.semantickernel.orchestration.PromptExecutionSettings; @@ -23,14 +22,13 @@ import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; +import javax.annotation.Nullable; import java.util.List; -import java.util.Map; -import java.util.function.Function; import java.util.stream.Collectors; public class ChatCompletionAgent extends KernelAgent { - ChatCompletionAgent( + private ChatCompletionAgent( String id, String name, String description, @@ -61,43 +59,49 @@ public class ChatCompletionAgent extends KernelAgent { * @return A Mono containing the agent response */ @Override - public Mono>>> invokeAsync(List> messages, AgentThread thread, AgentInvokeOptions options) { - - Mono chatHistoryFromThread = this.ensureThreadExistsAsync(messages, thread, ChatHistoryAgentThread::new) - .cast(ChatHistoryAgentThread.class) - .map(ChatHistoryAgentThread::getChatHistory) - .flatMap(threadChatHistory -> { - return Mono.just(new ChatHistory(threadChatHistory.getMessages())); - }); - - - Mono>> updatedChatHistory = chatHistoryFromThread.flatMap( - chatHistory -> internalInvokeAsync( - this.getName(), - chatHistory, - options - ) - ); + public Mono>>> invokeAsync( + List> messages, + AgentThread thread, + @Nullable AgentInvokeOptions options + ) { + return ensureThreadExistsWithMessagesAsync(messages, thread, ChatHistoryAgentThread::new) + .cast(ChatHistoryAgentThread.class) + .flatMap(agentThread -> { + // Extract the chat history from the thread + ChatHistory history = new ChatHistory( + agentThread.getChatHistory().getMessages() + ); - return updatedChatHistory.flatMap(chatMessageContents -> { - return Flux.fromIterable(chatMessageContents) - .concatMap(chatMessageContent -> this.notifyThreadOfNewMessageAsync(thread, chatMessageContent)) - .then(Mono.just(chatMessageContents)); // return the original list - }).flatMap(chatMessageContents -> { - return Mono.just(chatMessageContents.stream() - .map(chatMessageContent -> { - return new AgentResponseItem>( - chatMessageContent, - thread); - }).collect(Collectors.toList())); - }); + // Invoke the agent with the chat history + return internalInvokeAsync( + history, + options + ) + .flatMapMany(Flux::fromIterable) + // notify on the new thread instance + .concatMap(agentMessage -> { + // Set the author name for the message + agentMessage.setAuthorName(this.name); + + return this.notifyThreadOfNewMessageAsync(agentThread, agentMessage).thenReturn(agentMessage); + }) + .collectList() + .map(chatMessageContents -> + chatMessageContents.stream() + .map(message -> new AgentResponseItem>(message, agentThread)) + .collect(Collectors.toList()) + ); + }); } private Mono>> internalInvokeAsync( - String agentName, ChatHistory history, - AgentInvokeOptions options + @Nullable AgentInvokeOptions options ) { + if (options == null) { + options = new AgentInvokeOptions(); + } + final Kernel kernel = options.getKernel() != null ? options.getKernel() : this.kernel; final KernelArguments arguments = mergeArguments(options.getKernelArguments()); final String additionalInstructions = options.getAdditionalInstructions(); @@ -112,13 +116,13 @@ private Mono>> internalInvokeAsync( ToolCallBehavior toolCallBehavior = invocationContext != null ? invocationContext.getToolCallBehavior() - : ToolCallBehavior.allowAllKernelFunctions(false); + : ToolCallBehavior.allowAllKernelFunctions(true); // Build base invocation context InvocationContext.Builder builder = InvocationContext.builder() .withPromptExecutionSettings(executionSettings) .withToolCallBehavior(toolCallBehavior) - .withReturnMode(InvocationReturnMode.FULL_HISTORY); + .withReturnMode(InvocationReturnMode.NEW_MESSAGES_ONLY); if (invocationContext != null) { builder = builder @@ -129,7 +133,7 @@ private Mono>> internalInvokeAsync( InvocationContext agentInvocationContext = builder.build(); - return formatInstructionsAsync(kernel, arguments, agentInvocationContext).flatMap( + return renderInstructionsAsync(kernel, arguments, agentInvocationContext).flatMap( instructions -> { // Create a new chat history with the instructions ChatHistory chat = new ChatHistory( @@ -144,24 +148,26 @@ private Mono>> internalInvokeAsync( )); } + // Add the chat history to the new chat chat.addAll(history); - int previousHistorySize = chat.getMessages().size(); - - return chatCompletionService.getChatMessageContentsAsync(chat, kernel, agentInvocationContext) - .map(chatMessageContents -> { - return chatMessageContents.subList( - previousHistorySize, - chatMessageContents.size()); - }); + + return chatCompletionService.getChatMessageContentsAsync(chat, kernel, agentInvocationContext); } ); - } catch (ServiceNotFoundException e) { throw new RuntimeException(e); } } + + @Override + public Mono notifyThreadOfNewMessageAsync(AgentThread thread, ChatMessageContent message) { + return Mono.defer(() -> { + return thread.onNewMessageAsync(message); + }); + } + /** * Builder for creating instances of ChatCompletionAgent. */ @@ -174,71 +180,123 @@ public static class Builder implements SemanticKernelBuilder deleteAsync() { return Mono.fromRunnable(chatHistory::clear); } + /** + * Create a copy of the thread. + * + * @return A new instance of the thread. + */ + @Override + public ChatHistoryAgentThread copy() { + return new ChatHistoryAgentThread(this.id, new ChatHistory(chatHistory.getMessages())); + } + @Override public Mono onNewMessageAsync(ChatMessageContent newMessage) { return Mono.fromRunnable(() -> { @@ -61,4 +73,41 @@ public Mono onNewMessageAsync(ChatMessageContent newMessage) { public List> getMessages() { return chatHistory.getMessages(); } + + + public static Builder builder() { + return new Builder(); + } + + public static class Builder implements SemanticKernelBuilder { + private String id; + private ChatHistory chatHistory; + + /** + * Set the ID of the thread. + * + * @param id The ID of the thread. + * @return The builder instance. + */ + public Builder withId(String id) { + this.id = id; + return this; + } + + /** + * Set the chat history. + * + * @param chatHistory The chat history. + * @return The builder instance. + */ + public Builder withChatHistory(ChatHistory chatHistory) { + this.chatHistory = chatHistory; + return this; + } + + @Override + public ChatHistoryAgentThread build() { + return new ChatHistoryAgentThread(id, chatHistory); + } + } } diff --git a/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/agents/CompletionAgent.java b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/agents/CompletionAgent.java index 180b0c804..1e5a665a9 100644 --- a/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/agents/CompletionAgent.java +++ b/samples/semantickernel-concepts/semantickernel-syntax-examples/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/agents/CompletionAgent.java @@ -54,22 +54,13 @@ public static void main(String[] args) { .buildAsyncClient(); } + System.out.println("------------------------"); + ChatCompletionService chatCompletion = OpenAIChatCompletion.builder() .withModelId(MODEL_ID) .withOpenAIAsyncClient(client) .build(); - System.out.println("------------------------"); - - ContextVariableTypes.addGlobalConverter( - new ContextVariableTypeConverter<>( - GitHubModel.Issue.class, - o -> (GitHubModel.Issue) o, - o -> o.toString(), - s -> null - ) - ); - Kernel kernel = Kernel.builder() .withAIService(ChatCompletionService.class, chatCompletion) .withPlugin(KernelPluginFactory.createFromObject(new GitHubPlugin(GITHUB_PAT), @@ -78,6 +69,12 @@ public static void main(String[] args) { InvocationContext invocationContext = InvocationContext.builder() .withToolCallBehavior(ToolCallBehavior.allowAllKernelFunctions(true)) + .withContextVariableConverter(new ContextVariableTypeConverter<>( + GitHubModel.Issue.class, + o -> (GitHubModel.Issue) o, + o -> o.toString(), + s -> null + )) .build(); ChatCompletionAgent agent = ChatCompletionAgent.builder() @@ -110,8 +107,6 @@ public static void main(String[] args) { ).build(); ChatHistoryAgentThread agentThread = new ChatHistoryAgentThread(); - agentThread.createAsync().block(); - Scanner scanner = new Scanner(System.in); while (true) { @@ -136,7 +131,10 @@ public static void main(String[] args) { .build() ).block(); - System.out.println("> " + response.get(response.size() - 1).getMessage()); + var lastResponse = response.get(response.size() - 1); + + System.out.println("> " + lastResponse.getMessage()); + agentThread = (ChatHistoryAgentThread) lastResponse.getThread(); } } } diff --git a/semantickernel-api/src/main/java/com/microsoft/semantickernel/agents/AgentInvokeOptions.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/agents/AgentInvokeOptions.java index a542c40a5..d5a99b5ae 100644 --- a/semantickernel-api/src/main/java/com/microsoft/semantickernel/agents/AgentInvokeOptions.java +++ b/semantickernel-api/src/main/java/com/microsoft/semantickernel/agents/AgentInvokeOptions.java @@ -5,6 +5,8 @@ import com.microsoft.semantickernel.orchestration.InvocationContext; import com.microsoft.semantickernel.semanticfunctions.KernelArguments; +import javax.annotation.Nullable; + /** * Options for invoking an agent. */ @@ -15,6 +17,12 @@ public class AgentInvokeOptions { private final String additionalInstructions; private final InvocationContext invocationContext; + /** + * Default constructor for AgentInvokeOptions. + */ + public AgentInvokeOptions() { + this(null, null, null, null); + } /** * Constructor for AgentInvokeOptions. @@ -24,8 +32,10 @@ public class AgentInvokeOptions { * @param additionalInstructions Additional instructions for the agent. * @param invocationContext The invocation context. */ - public AgentInvokeOptions(KernelArguments KernelArguments, Kernel kernel, String additionalInstructions, - InvocationContext invocationContext) { + public AgentInvokeOptions(@Nullable KernelArguments KernelArguments, + @Nullable Kernel kernel, + @Nullable String additionalInstructions, + @Nullable InvocationContext invocationContext) { this.KernelArguments = KernelArguments; this.kernel = kernel; this.additionalInstructions = additionalInstructions; @@ -116,10 +126,10 @@ public Builder withInvocationContext(InvocationContext invocationContext) { @Override public AgentInvokeOptions build() { return new AgentInvokeOptions( - kernelArguments != null ? kernelArguments : com.microsoft.semantickernel.semanticfunctions.KernelArguments.builder().build(), - kernel != null ? kernel : Kernel.builder().build(), + kernelArguments, + kernel, additionalInstructions, - invocationContext + invocationContext ); } } diff --git a/semantickernel-api/src/main/java/com/microsoft/semantickernel/agents/AgentThread.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/agents/AgentThread.java index 0c83751a9..d369d999d 100644 --- a/semantickernel-api/src/main/java/com/microsoft/semantickernel/agents/AgentThread.java +++ b/semantickernel-api/src/main/java/com/microsoft/semantickernel/agents/AgentThread.java @@ -35,6 +35,13 @@ public interface AgentThread { */ boolean isDeleted(); + /** + * Create a copy of the thread. + * + * @return A new instance of the thread. + */ + AgentThread copy(); + /** * Handle a new message in the thread. * diff --git a/semantickernel-api/src/main/java/com/microsoft/semantickernel/agents/KernelAgent.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/agents/KernelAgent.java index a9af6f87e..e5f54a6d6 100644 --- a/semantickernel-api/src/main/java/com/microsoft/semantickernel/agents/KernelAgent.java +++ b/semantickernel-api/src/main/java/com/microsoft/semantickernel/agents/KernelAgent.java @@ -48,55 +48,69 @@ protected KernelAgent( this.template = template; } + /** + * Gets the agent's ID. + * + * @return The agent's ID + */ public String getId() { return id; } + /** + * Gets the agent's name. + * + * @return The agent's name + */ public String getName() { return name; } + /** + * Gets the agent's description. + * + * @return The agent's description + */ public String getDescription() { return description; } + /** + * Gets the kernel used by the agent. + * + * @return The kernel used by the agent + */ public Kernel getKernel() { return kernel; } + /** + * Gets the invocation context used by the agent. + * + * @return The invocation context used by the agent + */ public KernelArguments getKernelArguments() { return kernelArguments; } + /** + * Gets the invocation context used by the agent. + * + * @return The invocation context used by the agent + */ public String getInstructions() { return instructions; } + /** + * Gets the invocation context used by the agent. + * + * @return The invocation context used by the agent + */ public PromptTemplate getTemplate() { return template; } - protected Mono ensureThreadExistsAsync(List> messages, AgentThread thread, Supplier constructor) { - return Mono.defer(() -> { - T newThread = thread != null ? (T) thread : constructor.get(); - - return newThread.createAsync() - .thenMany(Flux.fromIterable(messages)) - .concatMap(message -> { - return notifyThreadOfNewMessageAsync(newThread, message) - .then(Mono.just(message)); - }) - .then(Mono.just(newThread)); - }); - } - - @Override - public Mono notifyThreadOfNewMessageAsync(AgentThread thread, ChatMessageContent message) { - return Mono.defer(() -> { - return thread.onNewMessageAsync(message); - }); - } - /** * Merges the provided arguments with the current arguments. @@ -127,11 +141,27 @@ protected KernelArguments mergeArguments(KernelArguments arguments) { * @param context The context to use for formatting. * @return A Mono that resolves to the formatted instructions. */ - protected Mono formatInstructionsAsync(Kernel kernel, KernelArguments arguments, InvocationContext context) { + protected Mono renderInstructionsAsync(Kernel kernel, KernelArguments arguments, InvocationContext context) { if (template != null) { return template.renderAsync(kernel, arguments, context); } else { return Mono.just(instructions); } } + + protected Mono ensureThreadExistsWithMessagesAsync(List> messages, AgentThread thread, Supplier threadSupplier) { + return Mono.defer(() -> { + // Check if the thread already exists + // If it does, we can work with a copy of it + AgentThread newThread = thread == null ? threadSupplier.get() : thread.copy(); + + return newThread.createAsync() + .thenMany(Flux.fromIterable(messages)) + .concatMap(message -> { + return notifyThreadOfNewMessageAsync(newThread, message) + .then(Mono.just(message)); + }) + .then(Mono.just((T) newThread)); + }); + } } diff --git a/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/chatcompletion/ChatMessageContent.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/chatcompletion/ChatMessageContent.java index 9784648b3..5769b5f1f 100644 --- a/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/chatcompletion/ChatMessageContent.java +++ b/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/chatcompletion/ChatMessageContent.java @@ -24,6 +24,7 @@ */ public class ChatMessageContent extends KernelContentImpl { + private String authorName; private final AuthorRole authorRole; @Nullable private final String content; @@ -52,6 +53,28 @@ public ChatMessageContent( null); } + /** + * Creates a new instance of the {@link ChatMessageContent} class. Defaults to + * {@link ChatMessageContentType#TEXT} content type. + * + * @param authorRole the author role that generated the content + * @param authorName the author name + * @param content the content + */ + public ChatMessageContent( + AuthorRole authorRole, + String authorName, + String content) { + this( + authorRole, + authorName, + content, + null, + null, + null, + null); + } + /** * Creates a new instance of the {@link ChatMessageContent} class. Defaults to * {@link ChatMessageContentType#TEXT} content type. @@ -132,6 +155,27 @@ public ChatMessageContent( this.contentType = contentType; } + + /** + * Gets the author name that generated the content + * + * @return the author name that generated the content + */ + @Nullable + public String getAuthorName() { + return authorName; + } + + /** + * Sets the author name that generated the content + * + * @param authorName the author name that generated the content + */ + public void setAuthorName(String authorName) { + this.authorName = authorName; + } + + /** * Gets the author role that generated the content * From 9cfab020f575128f790cdaecf6591cfdba1a1583 Mon Sep 17 00:00:00 2001 From: Milder Hernandez Cagua Date: Thu, 24 Apr 2025 23:07:19 -0700 Subject: [PATCH 05/12] Remove openai package --- agents/semantickernel-agents-openai/pom.xml | 26 --------------------- 1 file changed, 26 deletions(-) delete mode 100644 agents/semantickernel-agents-openai/pom.xml diff --git a/agents/semantickernel-agents-openai/pom.xml b/agents/semantickernel-agents-openai/pom.xml deleted file mode 100644 index 815a0d5bf..000000000 --- a/agents/semantickernel-agents-openai/pom.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - 4.0.0 - - com.microsoft.semantic-kernel - semantickernel-parent - 1.4.4-SNAPSHOT - - - semantickernel-agents-openai - - - 17 - 17 - UTF-8 - - - - com.microsoft.semantic-kernel - semantickernel-api - - - - \ No newline at end of file From afaa50ce8e018e2cbbf508d1335ee74f866c943f Mon Sep 17 00:00:00 2001 From: Milder Hernandez Cagua Date: Thu, 24 Apr 2025 23:08:56 -0700 Subject: [PATCH 06/12] Remove openai package --- pom.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/pom.xml b/pom.xml index f1536e875..0d31715dd 100644 --- a/pom.xml +++ b/pom.xml @@ -77,7 +77,6 @@ data/semantickernel-data-azureaisearch data/semantickernel-data-jdbc data/semantickernel-data-redis - agents/semantickernel-agents-openai agents/semantickernel-agents-core From 3dcbb890cba4e23bec24c83aa4f3d7f1a425736c Mon Sep 17 00:00:00 2001 From: Milder Hernandez Cagua Date: Thu, 24 Apr 2025 23:27:05 -0700 Subject: [PATCH 07/12] Add EI_EXPOSE_REP updates --- .../agents/AgentInvokeOptions.java | 32 ++++++++++++++++--- .../agents/AgentResponseItem.java | 14 ++++++++ .../semantickernel/agents/KernelAgent.java | 2 +- 3 files changed, 42 insertions(+), 6 deletions(-) diff --git a/semantickernel-api/src/main/java/com/microsoft/semantickernel/agents/AgentInvokeOptions.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/agents/AgentInvokeOptions.java index d5a99b5ae..00847d159 100644 --- a/semantickernel-api/src/main/java/com/microsoft/semantickernel/agents/AgentInvokeOptions.java +++ b/semantickernel-api/src/main/java/com/microsoft/semantickernel/agents/AgentInvokeOptions.java @@ -4,6 +4,7 @@ import com.microsoft.semantickernel.builders.SemanticKernelBuilder; import com.microsoft.semantickernel.orchestration.InvocationContext; import com.microsoft.semantickernel.semanticfunctions.KernelArguments; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import javax.annotation.Nullable; @@ -12,7 +13,7 @@ */ public class AgentInvokeOptions { - private final KernelArguments KernelArguments; + private final KernelArguments kernelArguments; private final Kernel kernel; private final String additionalInstructions; private final InvocationContext invocationContext; @@ -27,33 +28,53 @@ public AgentInvokeOptions() { /** * Constructor for AgentInvokeOptions. * - * @param KernelArguments The arguments for the kernel function. + * @param kernelArguments The arguments for the kernel function. * @param kernel The kernel to use. * @param additionalInstructions Additional instructions for the agent. * @param invocationContext The invocation context. */ - public AgentInvokeOptions(@Nullable KernelArguments KernelArguments, + public AgentInvokeOptions(@Nullable KernelArguments kernelArguments, @Nullable Kernel kernel, @Nullable String additionalInstructions, @Nullable InvocationContext invocationContext) { - this.KernelArguments = KernelArguments; + this.kernelArguments = kernelArguments != null ? kernelArguments.copy() : null; this.kernel = kernel; this.additionalInstructions = additionalInstructions; this.invocationContext = invocationContext; } + /** + * Get the kernel arguments. + * + * @return The kernel arguments. + */ public KernelArguments getKernelArguments() { - return KernelArguments; + return kernelArguments; } + /** + * Get the kernel. + * + * @return The kernel. + */ public Kernel getKernel() { return kernel; } + /** + * Get additional instructions. + * + * @return The additional instructions. + */ public String getAdditionalInstructions() { return additionalInstructions; } + /** + * Get the invocation context. + * + * @return The invocation context. + */ public InvocationContext getInvocationContext() { return invocationContext; } @@ -80,6 +101,7 @@ public static class Builder implements SemanticKernelBuilder * @param kernelArguments The kernel arguments. * @return The builder. */ + @SuppressFBWarnings("EI_EXPOSE_REP2") public Builder withKernelArguments(KernelArguments kernelArguments) { this.kernelArguments = kernelArguments; return this; diff --git a/semantickernel-api/src/main/java/com/microsoft/semantickernel/agents/AgentResponseItem.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/agents/AgentResponseItem.java index 61ffdd6d0..f585bfeab 100644 --- a/semantickernel-api/src/main/java/com/microsoft/semantickernel/agents/AgentResponseItem.java +++ b/semantickernel-api/src/main/java/com/microsoft/semantickernel/agents/AgentResponseItem.java @@ -1,18 +1,32 @@ package com.microsoft.semantickernel.agents; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + public class AgentResponseItem { private final T message; private final AgentThread thread; + @SuppressFBWarnings("EI_EXPOSE_REP2") public AgentResponseItem(T message, AgentThread thread) { this.message = message; this.thread = thread; } + /** + * Gets the agent response message. + * + * @return The message. + */ public T getMessage() { return message; } + /** + * Gets the thread. + * + * @return The thread. + */ + @SuppressFBWarnings("EI_EXPOSE_REP") public AgentThread getThread() { return thread; } diff --git a/semantickernel-api/src/main/java/com/microsoft/semantickernel/agents/KernelAgent.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/agents/KernelAgent.java index e5f54a6d6..d70514326 100644 --- a/semantickernel-api/src/main/java/com/microsoft/semantickernel/agents/KernelAgent.java +++ b/semantickernel-api/src/main/java/com/microsoft/semantickernel/agents/KernelAgent.java @@ -41,7 +41,7 @@ protected KernelAgent( this.name = name; this.description = description; this.kernel = kernel; - this.kernelArguments = kernelArguments; + this.kernelArguments = kernelArguments != null ? kernelArguments.copy() : null; this.invocationContext = invocationContext != null ? invocationContext : InvocationContext.builder().withReturnMode(InvocationReturnMode.FULL_HISTORY).build(); this.instructions = instructions; From 3c0f2557cdc96d1a289af7b7954cb7fdca508fb0 Mon Sep 17 00:00:00 2001 From: Milder Hernandez Cagua Date: Thu, 24 Apr 2025 23:29:27 -0700 Subject: [PATCH 08/12] Add EI_EXPOSE_REP updates --- .../com/microsoft/semantickernel/agents/AgentInvokeOptions.java | 1 + .../java/com/microsoft/semantickernel/agents/KernelAgent.java | 2 ++ 2 files changed, 3 insertions(+) diff --git a/semantickernel-api/src/main/java/com/microsoft/semantickernel/agents/AgentInvokeOptions.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/agents/AgentInvokeOptions.java index 00847d159..3fb4cba18 100644 --- a/semantickernel-api/src/main/java/com/microsoft/semantickernel/agents/AgentInvokeOptions.java +++ b/semantickernel-api/src/main/java/com/microsoft/semantickernel/agents/AgentInvokeOptions.java @@ -48,6 +48,7 @@ public AgentInvokeOptions(@Nullable KernelArguments kernelArguments, * * @return The kernel arguments. */ + @SuppressFBWarnings("EI_EXPOSE_REP") public KernelArguments getKernelArguments() { return kernelArguments; } diff --git a/semantickernel-api/src/main/java/com/microsoft/semantickernel/agents/KernelAgent.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/agents/KernelAgent.java index d70514326..6731a9279 100644 --- a/semantickernel-api/src/main/java/com/microsoft/semantickernel/agents/KernelAgent.java +++ b/semantickernel-api/src/main/java/com/microsoft/semantickernel/agents/KernelAgent.java @@ -7,6 +7,7 @@ import com.microsoft.semantickernel.semanticfunctions.KernelArguments; import com.microsoft.semantickernel.semanticfunctions.PromptTemplate; import com.microsoft.semantickernel.services.chatcompletion.ChatMessageContent; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -89,6 +90,7 @@ public Kernel getKernel() { * * @return The invocation context used by the agent */ + @SuppressFBWarnings("EI_EXPOSE_REP") public KernelArguments getKernelArguments() { return kernelArguments; } From fff6b90828cb15682496176e58da5082b3ffe1e7 Mon Sep 17 00:00:00 2001 From: Milder Hernandez Cagua Date: Thu, 24 Apr 2025 23:35:11 -0700 Subject: [PATCH 09/12] Add EI_EXPOSE_REP updates --- .../agents/chatcompletion/ChatCompletionAgent.java | 2 ++ .../agents/chatcompletion/ChatHistoryAgentThread.java | 3 +++ 2 files changed, 5 insertions(+) diff --git a/agents/semantickernel-agents-core/src/main/java/com/microsoft/semantickernel/agents/chatcompletion/ChatCompletionAgent.java b/agents/semantickernel-agents-core/src/main/java/com/microsoft/semantickernel/agents/chatcompletion/ChatCompletionAgent.java index 2e9a0d7df..ba09bff53 100644 --- a/agents/semantickernel-agents-core/src/main/java/com/microsoft/semantickernel/agents/chatcompletion/ChatCompletionAgent.java +++ b/agents/semantickernel-agents-core/src/main/java/com/microsoft/semantickernel/agents/chatcompletion/ChatCompletionAgent.java @@ -19,6 +19,7 @@ import com.microsoft.semantickernel.services.chatcompletion.ChatCompletionService; import com.microsoft.semantickernel.services.chatcompletion.ChatHistory; import com.microsoft.semantickernel.services.chatcompletion.ChatMessageContent; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -230,6 +231,7 @@ public Builder withKernel(Kernel kernel) { * * @param KernelArguments The kernel arguments to use. */ + @SuppressFBWarnings("EI_EXPOSE_REP2") public Builder withKernelArguments(KernelArguments KernelArguments) { this.kernelArguments = KernelArguments; return this; diff --git a/agents/semantickernel-agents-core/src/main/java/com/microsoft/semantickernel/agents/chatcompletion/ChatHistoryAgentThread.java b/agents/semantickernel-agents-core/src/main/java/com/microsoft/semantickernel/agents/chatcompletion/ChatHistoryAgentThread.java index 3aa68ea1b..1a68f8c44 100644 --- a/agents/semantickernel-agents-core/src/main/java/com/microsoft/semantickernel/agents/chatcompletion/ChatHistoryAgentThread.java +++ b/agents/semantickernel-agents-core/src/main/java/com/microsoft/semantickernel/agents/chatcompletion/ChatHistoryAgentThread.java @@ -5,6 +5,7 @@ import com.microsoft.semantickernel.builders.SemanticKernelBuilder; import com.microsoft.semantickernel.services.chatcompletion.ChatHistory; import com.microsoft.semantickernel.services.chatcompletion.ChatMessageContent; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import reactor.core.publisher.Mono; import javax.annotation.Nonnull; @@ -35,6 +36,7 @@ public ChatHistoryAgentThread(String id, @Nullable ChatHistory chatHistory) { * * @return The chat history. */ + @SuppressFBWarnings("EI_EXPOSE_REP") public ChatHistory getChatHistory() { return chatHistory; } @@ -100,6 +102,7 @@ public Builder withId(String id) { * @param chatHistory The chat history. * @return The builder instance. */ + @SuppressFBWarnings("EI_EXPOSE_REP2") public Builder withChatHistory(ChatHistory chatHistory) { this.chatHistory = chatHistory; return this; From ae917651980f0397dd62bca64754d00fffcbfb36 Mon Sep 17 00:00:00 2001 From: Milder Hernandez Cagua Date: Fri, 25 Apr 2025 11:16:35 -0700 Subject: [PATCH 10/12] Make NEW_MESSAGES_ONLY the default for kernel agent invocation context --- .../com/microsoft/semantickernel/agents/KernelAgent.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/semantickernel-api/src/main/java/com/microsoft/semantickernel/agents/KernelAgent.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/agents/KernelAgent.java index 6731a9279..71e81951d 100644 --- a/semantickernel-api/src/main/java/com/microsoft/semantickernel/agents/KernelAgent.java +++ b/semantickernel-api/src/main/java/com/microsoft/semantickernel/agents/KernelAgent.java @@ -42,9 +42,10 @@ protected KernelAgent( this.name = name; this.description = description; this.kernel = kernel; - this.kernelArguments = kernelArguments != null ? kernelArguments.copy() : null; + this.kernelArguments = kernelArguments != null + ? kernelArguments.copy() : KernelArguments.builder().build(); this.invocationContext = invocationContext != null - ? invocationContext : InvocationContext.builder().withReturnMode(InvocationReturnMode.FULL_HISTORY).build(); + ? invocationContext : InvocationContext.builder().build(); this.instructions = instructions; this.template = template; } From c004ae05fb658f035b77848e28072cbad59db829 Mon Sep 17 00:00:00 2001 From: Milder Hernandez Cagua Date: Mon, 28 Apr 2025 09:54:47 -0700 Subject: [PATCH 11/12] Return Mono.error instead of runtime exception --- .../agents/chatcompletion/ChatCompletionAgent.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/agents/semantickernel-agents-core/src/main/java/com/microsoft/semantickernel/agents/chatcompletion/ChatCompletionAgent.java b/agents/semantickernel-agents-core/src/main/java/com/microsoft/semantickernel/agents/chatcompletion/ChatCompletionAgent.java index ba09bff53..82a64334e 100644 --- a/agents/semantickernel-agents-core/src/main/java/com/microsoft/semantickernel/agents/chatcompletion/ChatCompletionAgent.java +++ b/agents/semantickernel-agents-core/src/main/java/com/microsoft/semantickernel/agents/chatcompletion/ChatCompletionAgent.java @@ -157,7 +157,7 @@ private Mono>> internalInvokeAsync( ); } catch (ServiceNotFoundException e) { - throw new RuntimeException(e); + return Mono.error(e); } } From a19b09fddd661f4d43c8f868be9c009e2d7ec512 Mon Sep 17 00:00:00 2001 From: Milder Hernandez Cagua Date: Mon, 28 Apr 2025 11:36:09 -0700 Subject: [PATCH 12/12] Remove authorName from ChatMessageContent --- .../chatcompletion/ChatCompletionAgent.java | 7 +----- .../chatcompletion/ChatMessageContent.java | 23 ------------------- 2 files changed, 1 insertion(+), 29 deletions(-) diff --git a/agents/semantickernel-agents-core/src/main/java/com/microsoft/semantickernel/agents/chatcompletion/ChatCompletionAgent.java b/agents/semantickernel-agents-core/src/main/java/com/microsoft/semantickernel/agents/chatcompletion/ChatCompletionAgent.java index 82a64334e..f8423f3d7 100644 --- a/agents/semantickernel-agents-core/src/main/java/com/microsoft/semantickernel/agents/chatcompletion/ChatCompletionAgent.java +++ b/agents/semantickernel-agents-core/src/main/java/com/microsoft/semantickernel/agents/chatcompletion/ChatCompletionAgent.java @@ -80,12 +80,7 @@ public Mono>>> invokeAsync( ) .flatMapMany(Flux::fromIterable) // notify on the new thread instance - .concatMap(agentMessage -> { - // Set the author name for the message - agentMessage.setAuthorName(this.name); - - return this.notifyThreadOfNewMessageAsync(agentThread, agentMessage).thenReturn(agentMessage); - }) + .concatMap(agentMessage -> this.notifyThreadOfNewMessageAsync(agentThread, agentMessage).thenReturn(agentMessage)) .collectList() .map(chatMessageContents -> chatMessageContents.stream() diff --git a/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/chatcompletion/ChatMessageContent.java b/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/chatcompletion/ChatMessageContent.java index 5769b5f1f..0408860e9 100644 --- a/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/chatcompletion/ChatMessageContent.java +++ b/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/chatcompletion/ChatMessageContent.java @@ -23,8 +23,6 @@ * @param the type of the inner content within the messages */ public class ChatMessageContent extends KernelContentImpl { - - private String authorName; private final AuthorRole authorRole; @Nullable private final String content; @@ -155,27 +153,6 @@ public ChatMessageContent( this.contentType = contentType; } - - /** - * Gets the author name that generated the content - * - * @return the author name that generated the content - */ - @Nullable - public String getAuthorName() { - return authorName; - } - - /** - * Sets the author name that generated the content - * - * @param authorName the author name that generated the content - */ - public void setAuthorName(String authorName) { - this.authorName = authorName; - } - - /** * Gets the author role that generated the content *