Skip to content

Commit fe3cd43

Browse files
authored
Merge branch 'main' into main
2 parents 1df846b + 3ce740c commit fe3cd43

1,461 files changed

Lines changed: 144219 additions & 7647 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/maven-ci.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,8 @@ jobs:
127127
if: runner.os == 'Windows'
128128
run: |
129129
# -T1: single-threaded build so reactor order is respected (see Linux step).
130-
& "~\.mvnd\bin\mvnd.cmd" -B -T1 clean verify
130+
# -Dmvnd.maxLostKeepAlive=120: avoid StaleAddressException on long javadoc phases (mvnd#161).
131+
& "~\.mvnd\bin\mvnd.cmd" -B -T1 "-Dmvnd.maxLostKeepAlive=120" clean verify
131132
132133
- name: Upload coverage reports to Codecov
133134
if: runner.os == 'Linux'

.gitignore

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,10 +57,28 @@ CLAUDE.md
5757
##Log
5858
logs/
5959

60-
## boba-tea-shop example
60+
## Frontend (all frontend/ directories across examples and extensions)
61+
# Dependencies
62+
**/frontend/node_modules/
63+
# Build output
64+
**/frontend/dist/
65+
**/frontend/build/
66+
**/frontend/.output/
67+
# Vite / bundler caches
68+
**/frontend/.vite/
69+
**/frontend/.cache/
70+
**/frontend/.parcel-cache/
71+
# Type-check / tsc output
72+
**/frontend/*.tsbuildinfo
73+
# Env files (may contain secrets)
74+
**/frontend/.env.local
75+
**/frontend/.env.*.local
76+
# Coverage
77+
**/frontend/coverage/
78+
# Storybook
79+
**/frontend/storybook-static/
80+
# boba-tea-shop: generated static assets served by the supervisor-agent
6181
**/boba-tea-shop/supervisor-agent/**/static/
62-
**/boba-tea-shop/**/node_modules/
63-
**/boba-tea-shop/**/dist/
6482

6583
##agentscope
6684
.agentscope/

.licenserc.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,10 @@ header:
7474
- '**/vendor/**'
7575
- '**/docs/**'
7676
- '**/.prettierrc'
77+
- 'agentscope-examples/**/frontend/**'
78+
- '**/Dockerfile'
79+
- '**/src/main/resources/**'
80+
- '**/src/test/resources/**'
7781
comment: on-failure
7882

7983
license-location-threshold: 130

agentscope-core/src/main/java/io/agentscope/core/ReActAgent.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -828,6 +828,28 @@ private Mono<PostActingEvent> notifyPostActingHook(
828828
protected Mono<Msg> summarizing() {
829829
log.debug("Maximum iterations reached. Generating summary...");
830830

831+
// Handle pending tool calls that were not completed before max iterations
832+
if (hasPendingToolUse()) {
833+
List<ToolUseBlock> pendingTools = extractPendingToolCalls();
834+
log.warn(
835+
"Max iterations reached with {} pending tool calls. Adding error results.",
836+
pendingTools.size());
837+
838+
for (ToolUseBlock toolUse : pendingTools) {
839+
ToolResultBlock errorResult =
840+
buildErrorToolResult(
841+
toolUse.getId(),
842+
"Tool execution cancelled because maximum iterations limit ("
843+
+ maxIters
844+
+ ") was reached");
845+
846+
Msg errorResultMsg =
847+
ToolResultMessageBuilder.buildToolResultMsg(
848+
errorResult, toolUse, getName());
849+
memory.addMessage(errorResultMsg);
850+
}
851+
}
852+
831853
List<Msg> messageList = prepareSummaryMessages();
832854
GenerateOptions generateOptions = buildGenerateOptions();
833855

agentscope-core/src/main/java/io/agentscope/core/formatter/openai/DeepSeekFormatter.java

Lines changed: 45 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
* <li>No name field in messages (returns HTTP 400 if present)</li>
3030
* <li>System messages should be converted to user messages</li>
3131
* <li>Does NOT support strict parameter in tool definitions</li>
32-
* <li>reasoning_content must be kept within current turn but removed for previous turns</li>
32+
* <li>In thinking mode, reasoning_content is preserved for segments with tool calls</li>
3333
* </ul>
3434
*
3535
* <p>Usage:
@@ -85,8 +85,8 @@ protected boolean supportsStrict() {
8585
* <ul>
8686
* <li>No name field in messages</li>
8787
* <li>System messages converted to user</li>
88-
* <li>reasoning_content kept within current turn (after last user message)</li>
89-
* <li>reasoning_content removed for previous turns (before last user message)</li>
88+
* <li>In thinking mode, reasoning_content preserved for segments with tool calls</li>
89+
* <li>reasoning_content removed for segments without tool calls in thinking mode</li>
9090
* </ul>
9191
*
9292
* <p>This method is static to allow sharing with {@link DeepSeekMultiAgentFormatter}.
@@ -95,16 +95,51 @@ protected boolean supportsStrict() {
9595
* @return the fixed messages for DeepSeek API
9696
*/
9797
static List<OpenAIMessage> applyDeepSeekFixes(List<OpenAIMessage> messages) {
98-
// Find the last user message index to determine current turn boundary
9998
int lastUserIndex = findLastUserIndex(messages);
99+
boolean thinkingMode = messages.stream().anyMatch(m -> m.getReasoningContent() != null);
100+
boolean[] segHasTool = thinkingMode ? computeSegmentToolFlags(messages) : null;
100101

101102
List<OpenAIMessage> result = new ArrayList<>(messages.size());
102103
for (int i = 0; i < messages.size(); i++) {
103-
result.add(fixMessage(messages.get(i), i >= lastUserIndex));
104+
boolean isCurrentTurn = i >= lastUserIndex;
105+
boolean needReasoning =
106+
thinkingMode
107+
? (isCurrentTurn || (segHasTool != null && segHasTool[i]))
108+
: isCurrentTurn;
109+
result.add(fixMessage(messages.get(i), needReasoning));
104110
}
105111
return result;
106112
}
107113

114+
/**
115+
* Scans messages in a single pass to identify segments (between consecutive
116+
* user messages) that contain tool calls. Messages within such segments
117+
* are flagged to preserve their reasoning_content.
118+
*/
119+
private static boolean[] computeSegmentToolFlags(List<OpenAIMessage> messages) {
120+
boolean[] flags = new boolean[messages.size()];
121+
int prevUser = -1;
122+
for (int i = 0; i <= messages.size(); i++) {
123+
if (i == messages.size() || "user".equals(messages.get(i).getRole())) {
124+
if (prevUser >= 0) {
125+
// Check if segment (prevUser, i) has any tool call
126+
boolean hasTool = false;
127+
for (int j = prevUser + 1; j < i && !hasTool; j++) {
128+
OpenAIMessage m = messages.get(j);
129+
hasTool = m.getToolCalls() != null && !m.getToolCalls().isEmpty();
130+
}
131+
if (hasTool) {
132+
for (int j = prevUser + 1; j < i; j++) {
133+
flags[j] = true;
134+
}
135+
}
136+
}
137+
prevUser = i;
138+
}
139+
}
140+
return flags;
141+
}
142+
108143
/**
109144
* Append an empty user message if the conversation ends with an assistant message.
110145
*
@@ -133,12 +168,13 @@ private static int findLastUserIndex(List<OpenAIMessage> messages) {
133168
}
134169

135170
@SuppressWarnings("unchecked")
136-
private static OpenAIMessage fixMessage(OpenAIMessage msg, boolean isCurrentTurn) {
171+
private static OpenAIMessage fixMessage(OpenAIMessage msg, boolean needReasoning) {
137172
boolean isSystem = "system".equals(msg.getRole());
138173
boolean hasName = msg.getName() != null;
139174
boolean hasReasoning = msg.getReasoningContent() != null;
140-
// Remove reasoning_content for previous turns, keep for current turn
141-
boolean shouldRemoveReasoning = hasReasoning && !isCurrentTurn;
175+
// needReasoning is determined by applyDeepSeekFixes:
176+
// true = current turn, or segment had tool calls in thinking mode
177+
boolean shouldRemoveReasoning = hasReasoning && !needReasoning;
142178

143179
if (!isSystem && !hasName && !shouldRemoveReasoning) {
144180
return msg;
@@ -162,8 +198,7 @@ private static OpenAIMessage fixMessage(OpenAIMessage msg, boolean isCurrentTurn
162198
builder.toolCallId(msg.getToolCallId());
163199
}
164200

165-
// Keep reasoning_content only for current turn
166-
if (hasReasoning && isCurrentTurn) {
201+
if (needReasoning && hasReasoning) {
167202
builder.reasoningContent(msg.getReasoningContent());
168203
}
169204

agentscope-core/src/main/java/io/agentscope/core/model/AnthropicChatModel.java

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525
import io.agentscope.core.formatter.anthropic.AnthropicChatFormatter;
2626
import io.agentscope.core.formatter.anthropic.AnthropicResponseParser;
2727
import io.agentscope.core.message.Msg;
28+
import io.agentscope.core.model.transport.ProxyConfig;
29+
import java.net.Proxy;
2830
import java.time.Instant;
2931
import java.util.List;
3032
import org.slf4j.Logger;
@@ -74,14 +76,16 @@ public class AnthropicChatModel extends ChatModelBase {
7476
* @param defaultOptions default generation options
7577
* @param formatter the message formatter to use (null for default
7678
* Anthropic formatter)
79+
* @param proxyConfig the proxy configuration (null for no proxy)
7780
*/
7881
public AnthropicChatModel(
7982
String baseUrl,
8083
String apiKey,
8184
String modelName,
8285
boolean streamEnabled,
8386
GenerateOptions defaultOptions,
84-
AnthropicBaseFormatter formatter) {
87+
AnthropicBaseFormatter formatter,
88+
ProxyConfig proxyConfig) {
8589
this.baseUrl = baseUrl;
8690
this.apiKey = apiKey;
8791
this.modelName = modelName;
@@ -101,6 +105,12 @@ public AnthropicChatModel(
101105
clientBuilder.baseUrl(baseUrl);
102106
}
103107

108+
// Configure proxy if provided
109+
if (proxyConfig != null) {
110+
Proxy proxy = proxyConfig.toJavaProxy();
111+
clientBuilder.proxy(proxy);
112+
}
113+
104114
this.client = clientBuilder.build();
105115
}
106116

@@ -237,6 +247,7 @@ public static class Builder {
237247
private boolean streamEnabled = true;
238248
private GenerateOptions defaultOptions;
239249
private AnthropicBaseFormatter formatter;
250+
private ProxyConfig proxyConfig;
240251

241252
/**
242253
* Sets the base URL for the Anthropic API.
@@ -304,14 +315,35 @@ public Builder formatter(AnthropicBaseFormatter formatter) {
304315
return this;
305316
}
306317

318+
/**
319+
* Sets the proxy configuration for HTTP traffic.
320+
*
321+
* <p>The Anthropic Java SDK does not support proxy authentication or {@code nonProxyHosts}. Only
322+
* {@link ProxyConfig#toJavaProxy()} is used; {@code username}, {@code password}, and {@code nonProxyHosts} on
323+
* {@link ProxyConfig} have no effect.
324+
*
325+
* @param proxyConfig the proxy configuration (see {@link ProxyConfig})
326+
* @return this builder
327+
*/
328+
public Builder proxy(ProxyConfig proxyConfig) {
329+
this.proxyConfig = proxyConfig;
330+
return this;
331+
}
332+
307333
/**
308334
* Builds the AnthropicChatModel instance.
309335
*
310336
* @return a new AnthropicChatModel
311337
*/
312338
public AnthropicChatModel build() {
313339
return new AnthropicChatModel(
314-
baseUrl, apiKey, modelName, streamEnabled, defaultOptions, formatter);
340+
baseUrl,
341+
apiKey,
342+
modelName,
343+
streamEnabled,
344+
defaultOptions,
345+
formatter,
346+
proxyConfig);
315347
}
316348
}
317349
}

0 commit comments

Comments
 (0)