Skip to content

Commit e4060e5

Browse files
authored
Merge branch 'main' into feat(models)_Kimi_Model_Adaptation
2 parents 46cd78a + c7093cf commit e4060e5

136 files changed

Lines changed: 14227 additions & 1167 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.

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ AgentScope Java 2.0 is a production-ready framework for building distributed, en
6161
- [**Workspace & Sandbox**](https://java.agentscope.io/v2/en/docs/harness/workspace.html) Run tools in isolated environments — local, Docker, Kubernetes, or AgentRun cloud sandbox.
6262
- [**Multi-Agent Orchestration**](https://java.agentscope.io/v2/en/docs/harness/subagent.html) Multiple subagent definition patterns with `agent_spawn` / `agent_send` and real-time event forwarding.
6363
- [**Distributed Deployment**](https://java.agentscope.io/v2/en/docs/others/going-to-production.html) True distributed session and memory management (Redis / MySQL / PostgreSQL / OSS / COS) with cross-replica session recovery.
64+
- [**Agent Evolution**](https://help.aliyun.com/zh/document_detail/3042583.html) Provides Agent observability and auditing, Agent evaluation and experimentation, and Agent asset management and continuous optimization.
6465

6566
<img src="./docs/imgs/landscape.png" alt="agentscope" width="100%"/>
6667

README_zh.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ AgentScope Java 2.0 是面向企业级、分布式、生产环境的智能体框
6161
- [**Workspace 与沙箱**](https://java.agentscope.io/v2/zh/docs/harness/workspace.html) 在隔离环境中执行工具 —— 本地、Docker、Kubernetes 或 AgentRun 云沙箱。
6262
- [**多智能体编排**](https://java.agentscope.io/v2/zh/docs/harness/subagent.html) 多种子智能体定义模式,`agent_spawn` / `agent_send` + 事件流实时转发。
6363
- [**分布式部署**](https://java.agentscope.io/v2/zh/docs/others/going-to-production.html) 真正的分布式 session 与 memory 管理(Redis / MySQL / PostgreSQL / OSS / COS),跨副本 session 恢复。
64+
- [**智能体进化**](https://help.aliyun.com/zh/document_detail/3042583.html) 提供 Agent 全栈观测与审计、Agent 评估与实验、Agent 资产管理与持续优化等能力。
6465

6566
<img src="./docs/imgs/landscape.png" alt="agentscope" width="100%"/>
6667

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

Lines changed: 53 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2133,13 +2133,31 @@ Flux<AgentEvent> reasoningStream(
21332133
Function<ModelCallInput, Flux<AgentEvent>> modelCallCore =
21342134
mci -> modelCallStream(context, mci, true);
21352135

2136+
StringBuilder transformedText = new StringBuilder();
2137+
AtomicBoolean sawTransformedTextDelta = new AtomicBoolean(false);
21362138
return MiddlewareChain.build(
21372139
middlewares,
21382140
ReActAgent.this,
21392141
rc,
21402142
MiddlewareBase::onModelCall,
21412143
modelCallCore)
2142-
.apply(new ModelCallInput(messages, tools, options, modelForCall()));
2144+
.apply(new ModelCallInput(messages, tools, options, modelForCall()))
2145+
.doOnNext(
2146+
event -> {
2147+
if (event instanceof TextBlockDeltaEvent textDelta) {
2148+
sawTransformedTextDelta.set(true);
2149+
if (textDelta.getDelta() != null) {
2150+
transformedText.append(textDelta.getDelta());
2151+
}
2152+
}
2153+
})
2154+
.doOnTerminate(
2155+
() -> {
2156+
if (sawTransformedTextDelta.get()
2157+
|| !context.getAccumulatedText().isEmpty()) {
2158+
context.replaceAccumulatedText(transformedText.toString());
2159+
}
2160+
});
21432161
}
21442162

21452163
private Flux<AgentEvent> modelCallStream(
@@ -2534,7 +2552,7 @@ private Flux<AgentEvent> runToolBatch(
25342552
for (ToolUseBlock tc : toolCalls) {
25352553
if (deniedIds.contains(tc.getId())) {
25362554
ToolResultBlock denied =
2537-
ToolResultBlock.text("Permission denied by user")
2555+
ToolResultBlock.text("Permission denied by rules")
25382556
.withIdAndName(tc.getId(), tc.getName())
25392557
.withState(ToolResultState.DENIED);
25402558
deniedEntries.add(Map.entry(tc, denied));
@@ -2555,7 +2573,7 @@ private Flux<AgentEvent> runToolBatch(
25552573
replyId,
25562574
use.getId(),
25572575
use.getName(),
2558-
"Permission denied by user"),
2576+
"Permission denied by rules"),
25592577
new ToolResultEndEvent(
25602578
replyId,
25612579
use.getId(),
@@ -3695,11 +3713,38 @@ public AgentState getAgentState(String userId, String sessionId) {
36953713
public void setPermissionMode(String userId, String sessionId, PermissionMode mode) {
36963714
Objects.requireNonNull(mode, "mode must not be null");
36973715
String sid = (sessionId == null || sessionId.isBlank()) ? defaultSessionId : sessionId;
3698-
String slot = slotKey(userId, sid);
3699-
AgentState s = getAgentState(userId, sid);
3700-
s.setPermissionContext(s.getPermissionContext().withMode(mode));
3701-
permissionEngineCache.put(slot, new PermissionEngine(s.getPermissionContext()));
3702-
saveAgentState(userId, sid);
3716+
AgentState state = getAgentState(userId, sid);
3717+
installPermissionContext(userId, sid, state, state.getPermissionContext().withMode(mode));
3718+
}
3719+
3720+
/**
3721+
* Replaces the permission context for one {@code (userId, sessionId)} slot, rebuilds that
3722+
* slot's permission engine, and persists the updated state.
3723+
*
3724+
* <p>An in-flight call keeps the call-scoped engine it started with. The replacement applies
3725+
* to subsequent calls on this slot and does not affect any other user or session.
3726+
*
3727+
* @param userId user identity for the slot (may be {@code null})
3728+
* @param sessionId session identity (falls back to the default session id when {@code null})
3729+
* @param permissionContext complete replacement context
3730+
*/
3731+
public void replacePermissionContext(
3732+
String userId, String sessionId, PermissionContextState permissionContext) {
3733+
Objects.requireNonNull(permissionContext, "permissionContext must not be null");
3734+
String sid = (sessionId == null || sessionId.isBlank()) ? defaultSessionId : sessionId;
3735+
AgentState state = getAgentState(userId, sid);
3736+
installPermissionContext(userId, sid, state, permissionContext);
3737+
}
3738+
3739+
private void installPermissionContext(
3740+
String userId,
3741+
String sessionId,
3742+
AgentState state,
3743+
PermissionContextState permissionContext) {
3744+
state.setPermissionContext(permissionContext);
3745+
permissionEngineCache.put(
3746+
slotKey(userId, sessionId), new PermissionEngine(permissionContext));
3747+
saveAgentState(userId, sessionId);
37033748
}
37043749

37053750
/**

agentscope-core/src/main/java/io/agentscope/core/agent/accumulator/ReasoningContext.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,16 @@ public String getAccumulatedText() {
239239
return textAcc.getAccumulated();
240240
}
241241

242+
/**
243+
* Replace accumulated text after {@code onModelCall} middleware transforms text delta events.
244+
*
245+
* @hidden
246+
* @param text text reconstructed from the transformed event stream
247+
*/
248+
public void replaceAccumulatedText(String text) {
249+
textAcc.replace(text);
250+
}
251+
242252
/**
243253
* Get the accumulated thinking content.
244254
*

agentscope-core/src/main/java/io/agentscope/core/agent/accumulator/TextAccumulator.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,4 +74,16 @@ public void reset() {
7474
public String getAccumulated() {
7575
return accumulated.toString();
7676
}
77+
78+
/**
79+
* Replace the accumulated text with content reconstructed from the model-call event stream.
80+
*
81+
* @hidden
82+
*/
83+
public void replace(String text) {
84+
accumulated.setLength(0);
85+
if (text != null) {
86+
accumulated.append(text);
87+
}
88+
}
7789
}

agentscope-core/src/main/java/io/agentscope/core/agent/accumulator/ToolCallsAccumulator.java

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,16 @@ void merge(ToolUseBlock block) {
6767
this.name = block.getName();
6868
}
6969

70-
// Merge parameters
70+
// Merge parameters. Skip null values so a partial/null map from an early
71+
// stream chunk cannot wipe previously accumulated non-null arguments.
7172
if (block.getInput() != null) {
72-
this.args.putAll(block.getInput());
73+
for (Map.Entry<String, Object> entry : block.getInput().entrySet()) {
74+
if (entry.getValue() != null) {
75+
this.args.put(entry.getKey(), entry.getValue());
76+
} else if (!this.args.containsKey(entry.getKey())) {
77+
this.args.put(entry.getKey(), null);
78+
}
79+
}
7380
}
7481

7582
// Accumulate raw content (for parsing complete JSON)
@@ -87,17 +94,27 @@ ToolUseBlock build() {
8794
Map<String, Object> finalArgs = new HashMap<>(args);
8895
String rawContentStr = this.rawContent.toString();
8996

90-
// If no parsed arguments but has raw JSON content, try to parse
91-
if (finalArgs.isEmpty() && rawContentStr.length() > 0) {
97+
// Always attempt to parse the fully accumulated raw JSON. Early stream chunks may
98+
// look like complete objects ({...}) but still contain null/incomplete values;
99+
// the final raw content is the source of truth for missing or null keys.
100+
if (!rawContentStr.isEmpty()) {
92101
try {
93102
@SuppressWarnings("unchecked")
94103
Map<String, Object> parsed =
95104
JsonUtils.getJsonCodec().fromJson(rawContentStr, Map.class);
96105
if (parsed != null) {
97-
finalArgs.putAll(parsed);
106+
for (Map.Entry<String, Object> entry : parsed.entrySet()) {
107+
if (entry.getValue() == null) {
108+
continue;
109+
}
110+
Object existing = finalArgs.get(entry.getKey());
111+
if (existing == null || !finalArgs.containsKey(entry.getKey())) {
112+
finalArgs.put(entry.getKey(), entry.getValue());
113+
}
114+
}
98115
}
99116
} catch (Exception ignored) {
100-
// Parsing failed, keep empty args
117+
// Parsing failed, keep previously merged args
101118
}
102119
}
103120

agentscope-core/src/main/java/io/agentscope/core/middleware/MiddlewareBase.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,10 @@ default Flux<AgentEvent> onActing(
112112
/**
113113
* Intercept the raw model API call.
114114
*
115+
* <p>Transformations to {@link io.agentscope.core.event.TextBlockDeltaEvent} instances in the
116+
* returned stream are reflected in the final response message. This allows middleware to
117+
* normalize model text before consumers, including native structured-output parsing, use it.
118+
*
115119
* @param agent the agent instance
116120
* @param ctx per-call runtime context (session, user, attributes)
117121
* @param input model-call input (messages, tools, options, model)

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,18 @@ private ModelContextWindows() {}
9191
Map.entry("glm-4-flash-250414", 128_000),
9292
Map.entry("glm-4-long", 1_000_000));
9393

94+
public static final Map<String, Integer> MINIMAX =
95+
Map.ofEntries(
96+
Map.entry("minimax-m3", 1_000_000),
97+
Map.entry("minimax-m2.7-highspeed", 204_800),
98+
Map.entry("minimax-m2.7", 204_800),
99+
Map.entry("minimax-m2.5-highspeed", 204_800),
100+
Map.entry("minimax-m2.5", 204_800),
101+
Map.entry("minimax-m2.1-highspeed", 204_800),
102+
Map.entry("minimax-m2.1", 204_800),
103+
Map.entry("minimax-m2", 204_800),
104+
Map.entry("m2-her", 65_536));
105+
94106
public static final Map<String, Integer> ANTHROPIC =
95107
Map.ofEntries(
96108
Map.entry("claude-opus-4", 200_000),
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
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+
package io.agentscope.core.model;
17+
18+
/** Shared helpers for model providers. */
19+
public final class ModelProviderSupport {
20+
21+
private ModelProviderSupport() {}
22+
23+
public static <T> T findAssignableComponent(
24+
ModelCreationContext context, Class<T> componentType) {
25+
for (Object value : context.getComponents().values()) {
26+
if (componentType.isInstance(value)) {
27+
return componentType.cast(value);
28+
}
29+
}
30+
return null;
31+
}
32+
33+
public static String firstNonBlank(String... values) {
34+
if (values == null) {
35+
return null;
36+
}
37+
for (String value : values) {
38+
String normalized = trimToNull(value);
39+
if (normalized != null) {
40+
return normalized;
41+
}
42+
}
43+
return null;
44+
}
45+
46+
public static String trimToNull(String value) {
47+
if (value == null) {
48+
return null;
49+
}
50+
String trimmed = value.trim();
51+
return trimmed.isEmpty() ? null : trimmed;
52+
}
53+
54+
public static Integer intOption(ModelCreationContext context, String key) {
55+
Object value = context.option(key);
56+
if (value == null) {
57+
return null;
58+
}
59+
if (value instanceof Number number) {
60+
return number.intValue();
61+
}
62+
throw new IllegalArgumentException(
63+
"ModelCreationContext option " + key + " must be a number");
64+
}
65+
66+
public static Boolean booleanOption(ModelCreationContext context, String key) {
67+
Object value = context.option(key);
68+
if (value == null) {
69+
return null;
70+
}
71+
if (value instanceof Boolean bool) {
72+
return bool;
73+
}
74+
throw new IllegalArgumentException(
75+
"ModelCreationContext option " + key + " must be a boolean");
76+
}
77+
78+
public static String stringOption(ModelCreationContext context, String key) {
79+
Object value = context.option(key);
80+
if (value == null) {
81+
return null;
82+
}
83+
if (value instanceof String text) {
84+
return trimToNull(text);
85+
}
86+
throw new IllegalArgumentException(
87+
"ModelCreationContext option " + key + " must be a string");
88+
}
89+
}

0 commit comments

Comments
 (0)