Skip to content

Commit 7616103

Browse files
authored
feat(boba-tea-shop): improve stability of Milk Tea DemoBoba tea shop OpenAI (#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
1 parent 4291f0d commit 7616103

8 files changed

Lines changed: 245 additions & 28 deletions

File tree

agentscope-core/src/main/java/io/agentscope/core/model/transport/JdkHttpTransport.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,7 @@ public Flux<String> stream(HttpRequest request) {
182182

183183
return Mono.fromCompletionStage(future)
184184
.flatMapMany(this::processStreamResponse)
185+
.publishOn(Schedulers.boundedElastic())
185186
.onErrorMap(
186187
e -> !(e instanceof HttpTransportException),
187188
e -> {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
/*
2+
* Copyright 2024-2026 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.agentscope.examples.bobatea.business.config;
18+
19+
import io.agentscope.core.formatter.dashscope.DashScopeChatFormatter;
20+
import io.agentscope.core.formatter.openai.OpenAIChatFormatter;
21+
import io.agentscope.core.model.DashScopeChatModel;
22+
import io.agentscope.core.model.Model;
23+
import io.agentscope.core.model.OpenAIChatModel;
24+
import org.slf4j.Logger;
25+
import org.slf4j.LoggerFactory;
26+
import org.springframework.beans.factory.annotation.Value;
27+
import org.springframework.context.annotation.Bean;
28+
import org.springframework.context.annotation.Configuration;
29+
30+
/**
31+
* AgentScope Model and Formatter Configuration
32+
* Supports both DashScope and OpenAI model providers
33+
*/
34+
@Configuration
35+
public class AgentScopeModelConfig {
36+
private static final Logger logger = LoggerFactory.getLogger(AgentScopeModelConfig.class);
37+
38+
private static final String PROVIDER_DASHSCOPE = "dashscope";
39+
private static final String PROVIDER_OPENAI = "openai";
40+
41+
@Value("${agentscope.model.provider}")
42+
private String modelProvider;
43+
44+
@Value("${agentscope.dashscope.api-key}")
45+
private String dashscopeApiKey;
46+
47+
@Value("${agentscope.dashscope.model-name:qwen-max}")
48+
private String dashscopeModelName;
49+
50+
@Value("${agentscope.dashscope.base-url}")
51+
private String dashscopeBaseUrl;
52+
53+
@Value("${agentscope.openai.api-key}")
54+
private String openaiApiKey;
55+
56+
@Value("${agentscope.openai.model-name:gpt-5}")
57+
private String openaiModelName;
58+
59+
@Value("${agentscope.openai.base-url}")
60+
private String openaiBaseUrl;
61+
62+
@Bean
63+
public Model model() {
64+
if (PROVIDER_OPENAI.equalsIgnoreCase(modelProvider)) {
65+
logger.info(
66+
"Creating OpenAI Model with model: {}, baseUrl: {}",
67+
openaiModelName,
68+
openaiBaseUrl);
69+
OpenAIChatModel.Builder builder =
70+
OpenAIChatModel.builder()
71+
.apiKey(openaiApiKey)
72+
.modelName(openaiModelName)
73+
.stream(true)
74+
.formatter(new OpenAIChatFormatter());
75+
if (openaiBaseUrl != null && !openaiBaseUrl.isEmpty() && !openaiBaseUrl.equals("-")) {
76+
builder.baseUrl(openaiBaseUrl);
77+
}
78+
return builder.build();
79+
} else {
80+
logger.info(
81+
"Creating DashScope Model with model: {}, baseUrl: {}",
82+
dashscopeModelName,
83+
dashscopeBaseUrl);
84+
DashScopeChatModel.Builder builder =
85+
DashScopeChatModel.builder()
86+
.apiKey(dashscopeApiKey)
87+
.modelName(dashscopeModelName)
88+
.formatter(new DashScopeChatFormatter());
89+
if (dashscopeBaseUrl != null
90+
&& !dashscopeBaseUrl.isEmpty()
91+
&& !dashscopeBaseUrl.equals("-")) {
92+
builder.baseUrl(dashscopeBaseUrl);
93+
}
94+
return builder.build();
95+
}
96+
}
97+
}

agentscope-examples/boba-tea-shop/business-sub-agent/src/main/java/io/agentscope/examples/bobatea/business/config/AgentScopeRunner.java

Lines changed: 43 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,12 @@
2121
import io.agentscope.core.a2a.server.executor.runner.AgentRequestOptions;
2222
import io.agentscope.core.a2a.server.executor.runner.AgentRunner;
2323
import io.agentscope.core.agent.Event;
24-
import io.agentscope.core.formatter.dashscope.DashScopeChatFormatter;
2524
import io.agentscope.core.memory.autocontext.AutoContextConfig;
2625
import io.agentscope.core.memory.autocontext.AutoContextMemory;
2726
import io.agentscope.core.memory.mem0.Mem0LongTermMemory;
2827
import io.agentscope.core.message.Msg;
2928
import io.agentscope.core.message.MsgRole;
3029
import io.agentscope.core.message.TextBlock;
31-
import io.agentscope.core.model.DashScopeChatModel;
3230
import io.agentscope.core.model.Model;
3331
import io.agentscope.core.tool.Toolkit;
3432
import io.agentscope.examples.bobatea.business.utils.MonitoringHook;
@@ -41,6 +39,8 @@
4139
import java.util.concurrent.ConcurrentHashMap;
4240
import java.util.regex.Matcher;
4341
import java.util.regex.Pattern;
42+
import org.slf4j.Logger;
43+
import org.slf4j.LoggerFactory;
4444
import org.springframework.beans.factory.annotation.Value;
4545
import org.springframework.context.annotation.Bean;
4646
import org.springframework.context.annotation.Configuration;
@@ -55,23 +55,14 @@ public class AgentScopeRunner {
5555
@Value("${agentscope.dashscope.model-name}")
5656
String modelName;
5757

58+
private static final Logger logger = LoggerFactory.getLogger(AgentScopeRunner.class);
59+
5860
@Bean
59-
public AgentRunner agentRunner(AgentPromptConfig promptConfig, AiService aiService) {
61+
public AgentRunner agentRunner(
62+
AgentPromptConfig promptConfig, AiService aiService, Model model) {
6063

6164
Toolkit toolkit = new NacosToolkit();
6265

63-
NacosMcpServerManager mcpServerManager = new NacosMcpServerManager(aiService);
64-
NacosMcpClientWrapper mcpClientWrapper =
65-
NacosMcpClientBuilder.create("business-mcp-server", mcpServerManager).build();
66-
toolkit.registerMcpClient(mcpClientWrapper).block();
67-
68-
Model model =
69-
DashScopeChatModel.builder().apiKey(apiKey).modelName(modelName).stream(
70-
true) // Enable streaming
71-
.enableThinking(true)
72-
.formatter(new DashScopeChatFormatter())
73-
.build();
74-
7566
AutoContextConfig autoContextConfig =
7667
AutoContextConfig.builder().tokenRatio(0.4).lastKeep(10).build();
7768
// Use AutoContextMemory, support context auto compression
@@ -86,7 +77,7 @@ public AgentRunner agentRunner(AgentPromptConfig promptConfig, AiService aiServi
8677
.model(model)
8778
.toolkit(toolkit);
8879

89-
return new CustomAgentRunner(builder);
80+
return new CustomAgentRunner(builder, aiService, toolkit);
9081
}
9182

9283
private static class CustomAgentRunner implements AgentRunner {
@@ -95,10 +86,19 @@ private static class CustomAgentRunner implements AgentRunner {
9586

9687
private final ReActAgent.Builder agentBuilder;
9788

89+
private final AiService aiService;
90+
91+
private final Toolkit toolkit;
92+
9893
private final Map<String, ReActAgent> agentCache;
9994

100-
private CustomAgentRunner(ReActAgent.Builder agentBuilder) {
95+
private volatile boolean mcpInitialized = false;
96+
97+
private CustomAgentRunner(
98+
ReActAgent.Builder agentBuilder, AiService aiService, Toolkit toolkit) {
10199
this.agentBuilder = agentBuilder;
100+
this.aiService = aiService;
101+
this.toolkit = toolkit;
102102
this.agentCache = new ConcurrentHashMap<>();
103103
}
104104

@@ -107,6 +107,7 @@ private ReActAgent buildReActAgent() {
107107
}
108108

109109
private ReActAgent buildReActAgent(String userId) {
110+
initializeMcpOnce();
110111
Mem0LongTermMemory longTermMemory =
111112
Mem0LongTermMemory.builder()
112113
.agentName("BusinessAgent")
@@ -117,6 +118,31 @@ private ReActAgent buildReActAgent(String userId) {
117118
return agentBuilder.longTermMemory(longTermMemory).build();
118119
}
119120

121+
private void initializeMcpOnce() {
122+
if (!mcpInitialized) {
123+
synchronized (this) {
124+
if (!mcpInitialized) {
125+
try {
126+
NacosMcpServerManager mcpServerManager =
127+
new NacosMcpServerManager(aiService);
128+
NacosMcpClientWrapper mcpClientWrapper =
129+
NacosMcpClientBuilder.create(
130+
"business-mcp-server", mcpServerManager)
131+
.build();
132+
toolkit.registerMcpClient(mcpClientWrapper).block();
133+
mcpInitialized = true;
134+
} catch (Exception e) {
135+
// Log the error and continue without MCP tools
136+
logger.warn(
137+
"Failed to initialize MCP client: "
138+
+ e.getMessage()
139+
+ " , will try later.");
140+
}
141+
}
142+
}
143+
}
144+
}
145+
120146
@Override
121147
public String getAgentName() {
122148
return buildReActAgent().getName();

agentscope-examples/boba-tea-shop/business-sub-agent/src/main/resources/application.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ agentscope:
2626
dashscope:
2727
api-key: ${MODEL_API_KEY:-}
2828
model-name: ${MODEL_NAME:qwen-max}
29+
base-url: ${MODEL_BASE_URL:-}
2930
openai:
3031
api-key: ${MODEL_API_KEY:-}
3132
model-name: ${MODEL_NAME:gpt-5}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
/*
2+
* Copyright 2024-2026 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.agentscope.examples.bobatea.consult.config;
18+
19+
import io.agentscope.core.formatter.dashscope.DashScopeChatFormatter;
20+
import io.agentscope.core.formatter.openai.OpenAIChatFormatter;
21+
import io.agentscope.core.model.DashScopeChatModel;
22+
import io.agentscope.core.model.Model;
23+
import io.agentscope.core.model.OpenAIChatModel;
24+
import org.slf4j.Logger;
25+
import org.slf4j.LoggerFactory;
26+
import org.springframework.beans.factory.annotation.Value;
27+
import org.springframework.context.annotation.Bean;
28+
import org.springframework.context.annotation.Configuration;
29+
30+
/**
31+
* AgentScope Model and Formatter Configuration
32+
* Supports both DashScope and OpenAI model providers
33+
*/
34+
@Configuration
35+
public class AgentScopeModelConfig {
36+
private static final Logger logger = LoggerFactory.getLogger(AgentScopeModelConfig.class);
37+
38+
private static final String PROVIDER_DASHSCOPE = "dashscope";
39+
private static final String PROVIDER_OPENAI = "openai";
40+
41+
@Value("${agentscope.model.provider}")
42+
private String modelProvider;
43+
44+
@Value("${agentscope.dashscope.api-key}")
45+
private String dashscopeApiKey;
46+
47+
@Value("${agentscope.dashscope.model-name:qwen-max}")
48+
private String dashscopeModelName;
49+
50+
@Value("${agentscope.dashscope.base-url}")
51+
private String dashscopeBaseUrl;
52+
53+
@Value("${agentscope.openai.api-key}")
54+
private String openaiApiKey;
55+
56+
@Value("${agentscope.openai.model-name:gpt-5}")
57+
private String openaiModelName;
58+
59+
@Value("${agentscope.openai.base-url}")
60+
private String openaiBaseUrl;
61+
62+
@Bean
63+
public Model model() {
64+
if (PROVIDER_OPENAI.equalsIgnoreCase(modelProvider)) {
65+
logger.info(
66+
"Creating OpenAI Model with model: {}, baseUrl: {}",
67+
openaiModelName,
68+
openaiBaseUrl);
69+
OpenAIChatModel.Builder builder =
70+
OpenAIChatModel.builder()
71+
.apiKey(openaiApiKey)
72+
.modelName(openaiModelName)
73+
.stream(true)
74+
.formatter(new OpenAIChatFormatter());
75+
if (openaiBaseUrl != null && !openaiBaseUrl.isEmpty() && !openaiBaseUrl.equals("-")) {
76+
builder.baseUrl(openaiBaseUrl);
77+
}
78+
return builder.build();
79+
} else {
80+
logger.info(
81+
"Creating DashScope Model with model: {}, baseUrl: {}",
82+
dashscopeModelName,
83+
dashscopeBaseUrl);
84+
DashScopeChatModel.Builder builder =
85+
DashScopeChatModel.builder()
86+
.apiKey(dashscopeApiKey)
87+
.modelName(dashscopeModelName)
88+
.formatter(new DashScopeChatFormatter());
89+
if (dashscopeBaseUrl != null
90+
&& !dashscopeBaseUrl.isEmpty()
91+
&& !dashscopeBaseUrl.equals("-")) {
92+
builder.baseUrl(dashscopeBaseUrl);
93+
}
94+
return builder.build();
95+
}
96+
}
97+
}

agentscope-examples/boba-tea-shop/consult-sub-agent/src/main/java/io/agentscope/examples/bobatea/consult/config/AgentScopeRunner.java

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,12 @@
2020
import io.agentscope.core.a2a.server.executor.runner.AgentRequestOptions;
2121
import io.agentscope.core.a2a.server.executor.runner.AgentRunner;
2222
import io.agentscope.core.agent.Event;
23-
import io.agentscope.core.formatter.dashscope.DashScopeChatFormatter;
2423
import io.agentscope.core.memory.autocontext.AutoContextConfig;
2524
import io.agentscope.core.memory.autocontext.AutoContextMemory;
2625
import io.agentscope.core.memory.mem0.Mem0LongTermMemory;
2726
import io.agentscope.core.message.Msg;
2827
import io.agentscope.core.message.MsgRole;
2928
import io.agentscope.core.message.TextBlock;
30-
import io.agentscope.core.model.DashScopeChatModel;
3129
import io.agentscope.core.model.Model;
3230
import io.agentscope.core.rag.Knowledge;
3331
import io.agentscope.core.rag.RAGMode;
@@ -56,18 +54,14 @@ public class AgentScopeRunner {
5654

5755
@Bean
5856
public AgentRunner agentRunner(
59-
AgentPromptConfig promptConfig, ConsultTools consultTools, Knowledge knowledge) {
57+
AgentPromptConfig promptConfig,
58+
ConsultTools consultTools,
59+
Knowledge knowledge,
60+
Model model) {
6061

6162
Toolkit toolkit = new NacosToolkit();
6263
toolkit.registerTool(consultTools);
6364

64-
Model model =
65-
DashScopeChatModel.builder().apiKey(apiKey).modelName(modelName).stream(
66-
true) // Enable streaming
67-
.enableThinking(true)
68-
.formatter(new DashScopeChatFormatter())
69-
.build();
70-
7165
AutoContextConfig autoContextConfig =
7266
AutoContextConfig.builder().tokenRatio(0.4).lastKeep(10).build();
7367
// Use AutoContextMemory, support context auto compression

agentscope-examples/boba-tea-shop/consult-sub-agent/src/main/resources/application.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ agentscope:
2626
dashscope:
2727
api-key: ${MODEL_API_KEY:-}
2828
model-name: ${MODEL_NAME:qwen-max}
29+
base-url: ${MODEL_BASE_URL:-}
2930
# dashscope RAG
3031
access-key-id: ${DASHSCOPE_ACCESS_KEY_ID:-}
3132
access-key-secret: ${DASHSCOPE_ACCESS_KEY_SECRET:-}

agentscope-examples/boba-tea-shop/frontend/src/base/i18n/zh.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ export default {
8686
},
8787
examples: {
8888
title: '常见问题示例',
89-
menu: '请为我推荐秋冬新品',
89+
menu: '请为我推荐新品奶茶',
9090
order: '我想查询我的订单',
9191
price: '老样子,来一杯!',
9292
feedback: '我要投诉服务或质量问题'

0 commit comments

Comments
 (0)