Skip to content

feat(boba-tea-shop): improve stability of Milk Tea DemoBoba tea shop OpenAI#464

Merged
AlbumenJ merged 3 commits intoagentscope-ai:mainfrom
LearningGp:boba-tea-shop-openai
Jan 7, 2026
Merged

feat(boba-tea-shop): improve stability of Milk Tea DemoBoba tea shop OpenAI#464
AlbumenJ merged 3 commits intoagentscope-ai:mainfrom
LearningGp:boba-tea-shop-openai

Conversation

@LearningGp
Copy link
Contributor

AgentScope-Java Version

1.0.7

Description

feat(boba-tea-shop): add support for OpenAI models in sub-agent'
fix(transport): resolve unexpected thread switching
feat(boba-tea-shop): optimize MCP client initialization logic for business-sub-agent

Checklist

Please check the following items before code is ready to be reviewed.

  • Code has been formatted with mvn spotless:apply
  • All tests are passing (mvn test)
  • Javadoc comments are complete and follow project conventions
  • Related documentation has been updated (e.g. links, examples, etc.)
  • Code is ready for review

Change-Id: I7db4d765607b007df037c1be79a6e2f3780b40ec
Change-Id: I73da48c7ee5527fcf59696dd4203c9c9a87356f9
…iness-sub-agent

Change-Id: I22703fd67d8a9a90f5a5f8eb1ac51cfefa7ecea0
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR enhances the boba-tea-shop example by adding OpenAI model provider support alongside the existing DashScope provider, fixing a thread switching issue in the HTTP transport layer, and optimizing MCP client initialization to be lazy-loaded with error recovery.

Key Changes:

  • Adds configurable multi-provider support (DashScope and OpenAI) through new AgentScopeModelConfig classes
  • Refactors model initialization from inline code to Spring Bean injection for better modularity
  • Implements lazy initialization of MCP client with double-checked locking pattern and error handling in business-sub-agent

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 15 comments.

Show a summary per file
File Description
frontend/src/base/i18n/zh.ts Updates Chinese localization text for example prompts
consult-sub-agent/src/main/resources/application.yml Adds base-url configuration support for DashScope
consult-sub-agent/src/main/java/.../AgentScopeRunner.java Refactors to inject Model bean instead of inline instantiation
consult-sub-agent/src/main/java/.../AgentScopeModelConfig.java New configuration class for multi-provider model setup
business-sub-agent/src/main/resources/application.yml Adds base-url configuration support for DashScope
business-sub-agent/src/main/java/.../AgentScopeRunner.java Refactors model injection and adds lazy MCP client initialization
business-sub-agent/src/main/java/.../AgentScopeModelConfig.java New configuration class for multi-provider model setup
agentscope-core/src/main/java/.../JdkHttpTransport.java Adds publishOn scheduler to fix thread switching issues in streaming

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +137 to +139
"Failed to initialize MCP client: "
+ e.getMessage()
+ " , will try later.");
Copy link

Copilot AI Jan 7, 2026

Choose a reason for hiding this comment

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

The error message construction uses string concatenation with the '+' operator. For better readability and following Java best practices, consider using String.format() or logger's built-in parameterization: logger.warn("Failed to initialize MCP client: {}, will try later.", e.getMessage())

Suggested change
"Failed to initialize MCP client: "
+ e.getMessage()
+ " , will try later.");
"Failed to initialize MCP client: {}, will try later.",
e.getMessage());

Copilot uses AI. Check for mistakes.
Comment on lines +84 to +94
DashScopeChatModel.Builder builder =
DashScopeChatModel.builder()
.apiKey(dashscopeApiKey)
.modelName(dashscopeModelName)
.formatter(new DashScopeChatFormatter());
if (dashscopeBaseUrl != null
&& !dashscopeBaseUrl.isEmpty()
&& !dashscopeBaseUrl.equals("-")) {
builder.baseUrl(dashscopeBaseUrl);
}
return builder.build();
Copy link

Copilot AI Jan 7, 2026

Choose a reason for hiding this comment

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

The DashScope model configuration is missing the .stream(true) and .enableThinking(true) options that were present in the previous implementation. These options were explicitly enabled in the original code and should be preserved to maintain the same functionality and behavior.

Copilot uses AI. Check for mistakes.
Comment on lines +121 to +144
private void initializeMcpOnce() {
if (!mcpInitialized) {
synchronized (this) {
if (!mcpInitialized) {
try {
NacosMcpServerManager mcpServerManager =
new NacosMcpServerManager(aiService);
NacosMcpClientWrapper mcpClientWrapper =
NacosMcpClientBuilder.create(
"business-mcp-server", mcpServerManager)
.build();
toolkit.registerMcpClient(mcpClientWrapper).block();
mcpInitialized = true;
} catch (Exception e) {
// Log the error and continue without MCP tools
logger.warn(
"Failed to initialize MCP client: "
+ e.getMessage()
+ " , will try later.");
}
}
}
}
}
Copy link

Copilot AI Jan 7, 2026

Choose a reason for hiding this comment

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

The method initializeMcpOnce() is missing documentation. While not public, this method implements a complex lazy initialization pattern with error handling. Adding a comment or Javadoc would help explain the double-checked locking pattern and the error recovery strategy of "will try later".

Copilot uses AI. Check for mistakes.
Comment on lines +1 to +97
/*
* Copyright 2024-2026 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package io.agentscope.examples.bobatea.business.config;

import io.agentscope.core.formatter.dashscope.DashScopeChatFormatter;
import io.agentscope.core.formatter.openai.OpenAIChatFormatter;
import io.agentscope.core.model.DashScopeChatModel;
import io.agentscope.core.model.Model;
import io.agentscope.core.model.OpenAIChatModel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
* AgentScope Model and Formatter Configuration
* Supports both DashScope and OpenAI model providers
*/
@Configuration
public class AgentScopeModelConfig {
private static final Logger logger = LoggerFactory.getLogger(AgentScopeModelConfig.class);

private static final String PROVIDER_DASHSCOPE = "dashscope";
private static final String PROVIDER_OPENAI = "openai";

@Value("${agentscope.model.provider}")
private String modelProvider;

@Value("${agentscope.dashscope.api-key}")
private String dashscopeApiKey;

@Value("${agentscope.dashscope.model-name:qwen-max}")
private String dashscopeModelName;

@Value("${agentscope.dashscope.base-url}")
private String dashscopeBaseUrl;

@Value("${agentscope.openai.api-key}")
private String openaiApiKey;

@Value("${agentscope.openai.model-name:gpt-5}")
private String openaiModelName;

@Value("${agentscope.openai.base-url}")
private String openaiBaseUrl;

@Bean
public Model model() {
if (PROVIDER_OPENAI.equalsIgnoreCase(modelProvider)) {
logger.info(
"Creating OpenAI Model with model: {}, baseUrl: {}",
openaiModelName,
openaiBaseUrl);
OpenAIChatModel.Builder builder =
OpenAIChatModel.builder()
.apiKey(openaiApiKey)
.modelName(openaiModelName)
.stream(true)
.formatter(new OpenAIChatFormatter());
if (openaiBaseUrl != null && !openaiBaseUrl.isEmpty() && !openaiBaseUrl.equals("-")) {
builder.baseUrl(openaiBaseUrl);
}
return builder.build();
} else {
logger.info(
"Creating DashScope Model with model: {}, baseUrl: {}",
dashscopeModelName,
dashscopeBaseUrl);
DashScopeChatModel.Builder builder =
DashScopeChatModel.builder()
.apiKey(dashscopeApiKey)
.modelName(dashscopeModelName)
.formatter(new DashScopeChatFormatter());
if (dashscopeBaseUrl != null
&& !dashscopeBaseUrl.isEmpty()
&& !dashscopeBaseUrl.equals("-")) {
builder.baseUrl(dashscopeBaseUrl);
}
return builder.build();
}
}
}
Copy link

Copilot AI Jan 7, 2026

Choose a reason for hiding this comment

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

There is significant code duplication between this class and the identical AgentScopeModelConfig class in consult-sub-agent. Consider extracting this configuration into a shared module or parent configuration class to follow the DRY (Don't Repeat Yourself) principle and reduce maintenance burden.

Copilot uses AI. Check for mistakes.
Comment on lines +132 to +139
toolkit.registerMcpClient(mcpClientWrapper).block();
mcpInitialized = true;
} catch (Exception e) {
// Log the error and continue without MCP tools
logger.warn(
"Failed to initialize MCP client: "
+ e.getMessage()
+ " , will try later.");
Copy link

Copilot AI Jan 7, 2026

Choose a reason for hiding this comment

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

The .block() call on a reactive operation could cause thread blocking issues, especially when called during initialization. Since this is wrapped in error handling and the flag prevents re-initialization, consider using .subscribe() with proper error handling instead, or document why synchronous blocking is necessary here. This is particularly important given that the PR description mentions fixing "unexpected thread switching" issues.

Suggested change
toolkit.registerMcpClient(mcpClientWrapper).block();
mcpInitialized = true;
} catch (Exception e) {
// Log the error and continue without MCP tools
logger.warn(
"Failed to initialize MCP client: "
+ e.getMessage()
+ " , will try later.");
toolkit.registerMcpClient(mcpClientWrapper)
.doOnSuccess(ignored -> mcpInitialized = true)
.doOnError(
error ->
logger.warn(
"Failed to initialize MCP client: "
+ error.getMessage()
+ " , will try later.",
error))
.subscribe();
} catch (Exception e) {
// Log the error and continue without MCP tools
logger.warn(
"Failed to initialize MCP client: "
+ e.getMessage()
+ " , will try later.",
e);

Copilot uses AI. Check for mistakes.
logger.warn(
"Failed to initialize MCP client: "
+ e.getMessage()
+ " , will try later.");
Copy link

Copilot AI Jan 7, 2026

Choose a reason for hiding this comment

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

The error message states "will try later", but there's no retry mechanism implemented. Once mcpInitialized is checked and the exception is caught, the flag remains false but initializeMcpOnce() will be called again. However, if the initialization continues to fail, this could lead to repeated failed initialization attempts on every agent creation. Consider either implementing an actual retry mechanism with backoff, or updating the error message to accurately reflect the behavior (e.g., "will retry on next agent creation").

Suggested change
+ " , will try later.");
+ " , will retry on next agent creation.");

Copilot uses AI. Check for mistakes.
Comment on lines +64 to +78
if (PROVIDER_OPENAI.equalsIgnoreCase(modelProvider)) {
logger.info(
"Creating OpenAI Model with model: {}, baseUrl: {}",
openaiModelName,
openaiBaseUrl);
OpenAIChatModel.Builder builder =
OpenAIChatModel.builder()
.apiKey(openaiApiKey)
.modelName(openaiModelName)
.stream(true)
.formatter(new OpenAIChatFormatter());
if (openaiBaseUrl != null && !openaiBaseUrl.isEmpty() && !openaiBaseUrl.equals("-")) {
builder.baseUrl(openaiBaseUrl);
}
return builder.build();
Copy link

Copilot AI Jan 7, 2026

Choose a reason for hiding this comment

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

The API keys are being logged when creating the model instances. While the log statement only shows the model name and baseUrl, ensure that the apiKey fields are not accidentally exposed in any logging framework's debug or trace levels. Consider reviewing logging configurations to prevent sensitive credential leakage.

Copilot uses AI. Check for mistakes.
Comment on lines +62 to +96
@Bean
public Model model() {
if (PROVIDER_OPENAI.equalsIgnoreCase(modelProvider)) {
logger.info(
"Creating OpenAI Model with model: {}, baseUrl: {}",
openaiModelName,
openaiBaseUrl);
OpenAIChatModel.Builder builder =
OpenAIChatModel.builder()
.apiKey(openaiApiKey)
.modelName(openaiModelName)
.stream(true)
.formatter(new OpenAIChatFormatter());
if (openaiBaseUrl != null && !openaiBaseUrl.isEmpty() && !openaiBaseUrl.equals("-")) {
builder.baseUrl(openaiBaseUrl);
}
return builder.build();
} else {
logger.info(
"Creating DashScope Model with model: {}, baseUrl: {}",
dashscopeModelName,
dashscopeBaseUrl);
DashScopeChatModel.Builder builder =
DashScopeChatModel.builder()
.apiKey(dashscopeApiKey)
.modelName(dashscopeModelName)
.formatter(new DashScopeChatFormatter());
if (dashscopeBaseUrl != null
&& !dashscopeBaseUrl.isEmpty()
&& !dashscopeBaseUrl.equals("-")) {
builder.baseUrl(dashscopeBaseUrl);
}
return builder.build();
}
}
Copy link

Copilot AI Jan 7, 2026

Choose a reason for hiding this comment

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

The model() method lacks input validation for the modelProvider field. If the configuration property agentscope.model.provider is not set or has an invalid value, the code will silently fall through to the DashScope provider without any warning. Consider adding validation and logging when an unknown provider is specified to help with debugging configuration issues.

Copilot uses AI. Check for mistakes.
Comment on lines +64 to +78
if (PROVIDER_OPENAI.equalsIgnoreCase(modelProvider)) {
logger.info(
"Creating OpenAI Model with model: {}, baseUrl: {}",
openaiModelName,
openaiBaseUrl);
OpenAIChatModel.Builder builder =
OpenAIChatModel.builder()
.apiKey(openaiApiKey)
.modelName(openaiModelName)
.stream(true)
.formatter(new OpenAIChatFormatter());
if (openaiBaseUrl != null && !openaiBaseUrl.isEmpty() && !openaiBaseUrl.equals("-")) {
builder.baseUrl(openaiBaseUrl);
}
return builder.build();
Copy link

Copilot AI Jan 7, 2026

Choose a reason for hiding this comment

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

The API keys are being logged when creating the model instances. While the log statement only shows the model name and baseUrl, ensure that the apiKey fields are not accidentally exposed in any logging framework's debug or trace levels. Consider reviewing logging configurations to prevent sensitive credential leakage.

Copilot uses AI. Check for mistakes.
@codecov
Copy link

codecov bot commented Jan 7, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@AlbumenJ AlbumenJ merged commit 7616103 into agentscope-ai:main Jan 7, 2026
11 checks passed
@LearningGp LearningGp deleted the boba-tea-shop-openai branch January 9, 2026 02:45
yaohuitc pushed a commit to yaohuitc/agentscope-java that referenced this pull request Jan 10, 2026
…OpenAI (agentscope-ai#464)

## AgentScope-Java Version

1.0.7

## Description

feat(boba-tea-shop): add support for OpenAI models in sub-agent'
fix(transport): resolve unexpected thread switching
feat(boba-tea-shop): optimize MCP client initialization logic for
business-sub-agent

## Checklist

Please check the following items before code is ready to be reviewed.

- [x]  Code has been formatted with `mvn spotless:apply`
- [x]  All tests are passing (`mvn test`)
- [x]  Javadoc comments are complete and follow project conventions
- [x] Related documentation has been updated (e.g. links, examples,
etc.)
- [x]  Code is ready for review
AlbumenJ pushed a commit that referenced this pull request Jan 19, 2026
#601)

## AgentScope-Java Version

1.0.8

## Description

Same as #464

## Checklist

Please check the following items before code is ready to be reviewed.

- [x]  Code has been formatted with `mvn spotless:apply`
- [x]  All tests are passing (`mvn test`)
- [x]  Javadoc comments are complete and follow project conventions
- [x] Related documentation has been updated (e.g. links, examples,
etc.)
- [x]  Code is ready for review
Alexxigang pushed a commit to Alexxigang/agentscope-java that referenced this pull request Jan 25, 2026
agentscope-ai#601)

## AgentScope-Java Version

1.0.8

## Description

Same as agentscope-ai#464

## Checklist

Please check the following items before code is ready to be reviewed.

- [x]  Code has been formatted with `mvn spotless:apply`
- [x]  All tests are passing (`mvn test`)
- [x]  Javadoc comments are complete and follow project conventions
- [x] Related documentation has been updated (e.g. links, examples,
etc.)
- [x]  Code is ready for review
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants