Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"java.compile.nullAnalysis.mode": "automatic",
"java.configuration.updateBuildConfiguration": "interactive"
}
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,12 +60,15 @@ Invoices samples are included in the data folder to make it easy to explore paym
This project provides the following features and technical patterns:
- Simple multi-agent supervisor architecture using **gpt-4o-mini** or **gpt-4o** on Azure Open AI.
- Exposing your business API as MCP tools for your agents using [spring-ai-mcp](https://docs.spring.io/spring-ai/reference/api/mcp/mcp-overview.html)
- **[NEW]** Using official **[langchain4j-agentic](https://github.com/langchain4j/langchain4j/tree/main/langchain4j-agentic)** module for multi-agent orchestration.
- Agents tools configuration and automatic tools invocations with [Langchain4j](https://github.com/langchain4j/langchain4j).
- Chat based conversation implemented as [React Single Page Application](https://react.fluentui.dev/?path=/docs/concepts-introduction--docs) with support for images upload.Supported images are invoices, receipts, bills jpeg/png files you want your virtual banking assistant to pay on your behalf.
- Images scanning and data extraction with Azure Document Intelligence using [prebuilt-invoice](https://learn.microsoft.com/en-us/azure/ai-services/document-intelligence/concept-invoice?view=doc-intel-4.0.0) model.
- Add a copilot app side-by-side to your existing business microservices hosted on [Azure Container Apps](https://azure.microsoft.com/en-us/products/container-apps).
- Automated Azure resources creation and solution deployment leveraging [Azure Developer CLI](https://learn.microsoft.com/en-us/azure/developer/azure-developer-cli/).

> **Migration to langchain4j-agentic**: This project has been migrated from a custom agent framework to the official langchain4j-agentic module. See the [Migration Guide](./docs/MIGRATION_TO_AGENTIC.md) for details on the changes and benefits.

For complex agents conversation implementation, read more about [Autogen framework](https://github.com/microsoft/autogen).

### Architecture
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public class AccountMCPClient {


public static void main(String[] args) {
var transport = new HttpClientSseClientTransport("http://localhost:8070");
var transport = new HttpClientSseClientTransport("http://account:8080/sse");

var client = McpClient.sync(transport).build();

Expand Down
1 change: 1 addition & 0 deletions app/copilot/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
**/target/
34 changes: 29 additions & 5 deletions app/copilot/copilot-backend/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,35 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-azure-open-ai</artifactId>
<version>${langchain4j.version}</version>
</dependency>
<!-- Source: https://mvnrepository.com/artifact/dev.langchain4j/langchain4j -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j</artifactId>
<version>1.10.0</version>
<scope>compile</scope>
</dependency>
<!-- Source: https://mvnrepository.com/artifact/dev.langchain4j/langchain4j-mcp -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-mcp</artifactId>
<version>1.10.0-beta18</version>
<scope>compile</scope>
</dependency>
<!-- Source: https://mvnrepository.com/artifact/dev.langchain4j/langchain4j-azure-open-ai -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-agentic</artifactId>
<version>1.10.0-beta18</version>
<scope>compile</scope>
</dependency>

<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-azure-open-ai</artifactId>
<version>1.10.0</version>
<scope>compile</scope>
</dependency>

<dependency>
<groupId>com.azure</groupId>
<artifactId>azure-identity</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
import com.azure.core.credential.TokenCredential;
import com.azure.core.http.policy.HttpLogDetailLevel;
import com.azure.core.http.policy.HttpLogOptions;
import dev.langchain4j.model.azure.AzureOpenAiChatModel;
import dev.langchain4j.model.chat.ChatModel;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
Expand All @@ -18,6 +20,9 @@ public class AzureOpenAIConfiguration {
@Value("${openai.service}")
String openAIServiceName;

@Value("${openai.endpoint:}")
String openAIEndpoint;

@Value("${openai.chatgpt.deployment}")
private String gptChatDeploymentModelId;

Expand All @@ -27,10 +32,31 @@ public AzureOpenAIConfiguration(TokenCredential tokenCredential) {
this.tokenCredential = tokenCredential;
}

/**
* Resolves the AI Foundry endpoint. If AZURE_OPENAI_ENDPOINT is set, use it directly.
* Otherwise, fall back to constructing from the service name (legacy OpenAI pattern).
*/
private String resolveEndpoint() {
if (openAIEndpoint != null && !openAIEndpoint.isBlank()) {
return openAIEndpoint;
}
return "https://%s.openai.azure.com".formatted(openAIServiceName);
}

@Bean
public ChatModel chatModel() {
String endpoint = resolveEndpoint();
return AzureOpenAiChatModel.builder()
.endpoint(endpoint)
.tokenCredential(tokenCredential)
.deploymentName(gptChatDeploymentModelId)
.build();
}

@Bean
@ConditionalOnProperty(name = "openai.tracing.enabled", havingValue = "true")
public OpenAIClient openAItracingEnabledClient() {
String endpoint = "https://%s.openai.azure.com".formatted(openAIServiceName);
String endpoint = resolveEndpoint();

var httpLogOptions = new HttpLogOptions();
// httpLogOptions.setPrettyPrintBody(true);
Expand All @@ -47,7 +73,7 @@ public OpenAIClient openAItracingEnabledClient() {
@Bean
@ConditionalOnProperty(name = "openai.tracing.enabled", havingValue = "false")
public OpenAIClient openAIDefaultClient() {
String endpoint = "https://%s.openai.azure.com".formatted(openAIServiceName);
String endpoint = resolveEndpoint();
return new OpenAIClientBuilder()
.endpoint(endpoint)
.credential(tokenCredential)
Expand All @@ -57,7 +83,7 @@ public OpenAIClient openAIDefaultClient() {
@Bean
@ConditionalOnProperty(name = "openai.tracing.enabled", havingValue = "true")
public OpenAIAsyncClient tracingEnabledAsyncClient() {
String endpoint = "https://%s.openai.azure.com".formatted(openAIServiceName);
String endpoint = resolveEndpoint();

var httpLogOptions = new HttpLogOptions();
httpLogOptions.setPrettyPrintBody(true);
Expand All @@ -73,7 +99,7 @@ public OpenAIAsyncClient tracingEnabledAsyncClient() {
@Bean
@ConditionalOnProperty(name = "openai.tracing.enabled", havingValue = "false")
public OpenAIAsyncClient defaultAsyncClient() {
String endpoint = "https://%s.openai.azure.com".formatted(openAIServiceName);
String endpoint = resolveEndpoint();
return new OpenAIClientBuilder()
.endpoint(endpoint)
.credential(tokenCredential)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,8 @@
// Copyright (c) Microsoft. All rights reserved.
package com.microsoft.openai.samples.assistant.config;

import com.azure.ai.documentintelligence.DocumentIntelligenceClient;
import com.azure.core.credential.TokenCredential;
import com.microsoft.openai.samples.assistant.invoice.DocumentIntelligenceInvoiceScanHelper;
import com.microsoft.openai.samples.assistant.langchain4j.agent.mcp.AccountMCPAgent;
import com.microsoft.openai.samples.assistant.langchain4j.agent.mcp.PaymentMCPAgent;
import com.microsoft.openai.samples.assistant.langchain4j.agent.mcp.TransactionHistoryMCPAgent;
import com.microsoft.openai.samples.assistant.proxy.BlobStorageProxy;
import com.microsoft.openai.samples.assistant.security.LoggedUserService;
import dev.langchain4j.model.chat.ChatLanguageModel;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -3,54 +3,109 @@

import com.microsoft.openai.samples.assistant.invoice.DocumentIntelligenceInvoiceScanHelper;
import com.microsoft.openai.samples.assistant.langchain4j.agent.SupervisorAgent;
import com.microsoft.openai.samples.assistant.langchain4j.agent.mcp.AccountMCPAgent;
import com.microsoft.openai.samples.assistant.langchain4j.agent.mcp.PaymentMCPAgent;
import com.microsoft.openai.samples.assistant.langchain4j.agent.mcp.TransactionHistoryMCPAgent;
import com.microsoft.openai.samples.assistant.langchain4j.agent.builder.AccountMCPAgentBuilder;
import com.microsoft.openai.samples.assistant.langchain4j.agent.builder.PaymentMCPAgentBuilder;
import com.microsoft.openai.samples.assistant.langchain4j.agent.builder.SupervisorAgentBuilder;
import com.microsoft.openai.samples.assistant.langchain4j.agent.builder.TransactionHistoryMCPAgentBuilder;
import com.microsoft.openai.samples.assistant.security.LoggedUserService;
import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.model.chat.ChatModel;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.List;

/**
* Configuration for MCP-based agents using langchain4j-agentic module.
* This configuration has been migrated from custom agent framework to use the official
* langchain4j-agentic builders for better maintainability and access to new features.
*/
@Configuration
public class MCPAgentsConfiguration {
@Value("${transactions.api.url}") String transactionsMCPServerUrl;
@Value("${accounts.api.url}") String accountsMCPServerUrl;
@Value("${payments.api.url}") String paymentsMCPServerUrl;

private final ChatLanguageModel chatLanguageModel;
private final ChatModel chatModel;
private final LoggedUserService loggedUserService;
private final DocumentIntelligenceInvoiceScanHelper documentIntelligenceInvoiceScanHelper;

public MCPAgentsConfiguration(ChatLanguageModel chatLanguageModel, LoggedUserService loggedUserService, DocumentIntelligenceInvoiceScanHelper documentIntelligenceInvoiceScanHelper) {
this.chatLanguageModel = chatLanguageModel;
public MCPAgentsConfiguration(
ChatModel chatModel,
LoggedUserService loggedUserService,
DocumentIntelligenceInvoiceScanHelper documentIntelligenceInvoiceScanHelper) {
this.chatModel = chatModel;
this.loggedUserService = loggedUserService;
this.documentIntelligenceInvoiceScanHelper = documentIntelligenceInvoiceScanHelper;
}

/**
* Creates the Account MCP Agent bean using the builder pattern.
* This agent handles account-related queries and operations.
*
* @return Account agent instance
*/
@Bean
public AccountMCPAgent accountMCPAgent() {
return new AccountMCPAgent(chatLanguageModel, loggedUserService.getLoggedUser().username(), accountsMCPServerUrl);
public Object accountMCPAgent() {
AccountMCPAgentBuilder builder = new AccountMCPAgentBuilder(
chatModel,
loggedUserService.getLoggedUser().username(),
accountsMCPServerUrl
);
// Using programmatic approach for consistent API across all agents
return builder.buildProgrammatic();
}

/**
* Creates the Transaction History MCP Agent bean using the builder pattern.
* This agent handles transaction history queries and searches.
*
* @return Transaction history agent instance
*/
@Bean
public TransactionHistoryMCPAgent transactionHistoryMCPAgent() {
return new TransactionHistoryMCPAgent(chatLanguageModel, loggedUserService.getLoggedUser().username(), transactionsMCPServerUrl,accountsMCPServerUrl);
public Object transactionHistoryMCPAgent() {
TransactionHistoryMCPAgentBuilder builder = new TransactionHistoryMCPAgentBuilder(
chatModel,
loggedUserService.getLoggedUser().username(),
transactionsMCPServerUrl,
accountsMCPServerUrl
);
// Using programmatic approach for consistent API across all agents
return builder.buildProgrammatic();
}

/**
* Creates the Payment MCP Agent bean using the builder pattern.
* This agent handles payment processing, invoice scanning, and payment submissions.
*
* @return Payment agent instance
*/
@Bean
public PaymentMCPAgent paymentMCPAgent() {
return new PaymentMCPAgent(chatLanguageModel,documentIntelligenceInvoiceScanHelper, loggedUserService.getLoggedUser().username(),transactionsMCPServerUrl,accountsMCPServerUrl, paymentsMCPServerUrl);
public Object paymentMCPAgent() {
PaymentMCPAgentBuilder builder = new PaymentMCPAgentBuilder(
chatModel,
documentIntelligenceInvoiceScanHelper,
loggedUserService.getLoggedUser().username(),
paymentsMCPServerUrl
);
// Using programmatic approach for consistent API across all agents
return builder.buildProgrammatic();
}

/**
* Creates the Supervisor Agent bean using the builder pattern.
* The supervisor routes user requests to the appropriate domain-specific agent.
*
* @return Supervisor agent instance
*/
@Bean
public SupervisorAgent supervisorAgent(ChatLanguageModel chatLanguageModel){
return new SupervisorAgent(chatLanguageModel,
List.of(accountMCPAgent(),
transactionHistoryMCPAgent(),
paymentMCPAgent()));

public SupervisorAgent supervisorAgent() {
SupervisorAgentBuilder builder = new SupervisorAgentBuilder(
chatModel,
accountMCPAgent(),
transactionHistoryMCPAgent(),
paymentMCPAgent()
);
// Using programmatic supervisor builder for multi-agent orchestration
return (SupervisorAgent) builder.buildProgrammatic();
}

}

Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ public record ChatAppRequest(
List<String> attachments,
ChatAppRequestContext context,
boolean stream,
String approach) {}
String approach,
String session_state) {}
Loading