diff --git a/assistant-agent-autoconfigure/pom.xml b/assistant-agent-autoconfigure/pom.xml
index 236b58eb..a4b817f5 100644
--- a/assistant-agent-autoconfigure/pom.xml
+++ b/assistant-agent-autoconfigure/pom.xml
@@ -6,7 +6,7 @@
com.alibaba.agent.assistant
assistant-agent
- 0.2.5
+ 0.2.6
assistant-agent-autoconfigure
@@ -88,4 +88,4 @@
-
\ No newline at end of file
+
diff --git a/assistant-agent-autoconfigure/src/main/java/com/alibaba/assistant/agent/autoconfigure/tools/WriteCodeTool.java b/assistant-agent-autoconfigure/src/main/java/com/alibaba/assistant/agent/autoconfigure/tools/WriteCodeTool.java
index 019a5fba..3200f752 100644
--- a/assistant-agent-autoconfigure/src/main/java/com/alibaba/assistant/agent/autoconfigure/tools/WriteCodeTool.java
+++ b/assistant-agent-autoconfigure/src/main/java/com/alibaba/assistant/agent/autoconfigure/tools/WriteCodeTool.java
@@ -232,12 +232,16 @@ private void saveToStore(ToolContext toolContext, GeneratedCode code) {
private static final String WRITE_CODE_DESCRIPTION = """
使用指定的名称和完整代码注册一个 Python 函数。
+ ⚠️ 四个参数都是必需的,缺一不可。**尤其是 `code`**:必须在本次调用中直接传入完整函数体的 Python 源码字符串,不能只传 functionName/description/parameters 然后期望后续再补 code —— 缺少 `code` 会立即报错 "Error: code is required"。
+
必需参数:
- functionName:函数名称(snake_case 格式,例如 'calculate_sum')
- description:函数功能的简要描述
- parameters:函数参数名的字符串数组。只写参数名,不要包含类型或描述。无参数时传空数组 []。
正确示例:["a", "b"]、["query"]、[]
- - code:完整的 Python 函数代码,包含 'def' 语句和完整实现
+ - code:完整的 Python 函数代码,必须以 'def function_name(...):' 开头并包含完整函数体;不能是空字符串、不能只写签名、不能省略
+
+ ⚠️ 一次性编排:对于一个完整任务,应在单次 `write_code` 中写出**覆盖全流程**的函数(含所有工具调用、分支、汇总、最终回复),而不是拆成 step0/step1/... 多次 write_code + execute_code。
代码编写规范:
1. 函数结构:
diff --git a/assistant-agent-common/pom.xml b/assistant-agent-common/pom.xml
index df2cc5c2..d3adf286 100644
--- a/assistant-agent-common/pom.xml
+++ b/assistant-agent-common/pom.xml
@@ -6,7 +6,7 @@
com.alibaba.agent.assistant
assistant-agent
- 0.2.5
+ 0.2.6
assistant-agent-common
@@ -43,4 +43,4 @@
-
\ No newline at end of file
+
diff --git a/assistant-agent-common/src/main/java/com/alibaba/assistant/agent/common/constant/CodeactStateKeys.java b/assistant-agent-common/src/main/java/com/alibaba/assistant/agent/common/constant/CodeactStateKeys.java
index 0064933c..7be9f821 100644
--- a/assistant-agent-common/src/main/java/com/alibaba/assistant/agent/common/constant/CodeactStateKeys.java
+++ b/assistant-agent-common/src/main/java/com/alibaba/assistant/agent/common/constant/CodeactStateKeys.java
@@ -156,6 +156,14 @@ private CodeactStateKeys() {
*/
public static final String EXPERIENCE_ALLOWED_REACT_TOOL_NAMES = "experience_allowed_react_tool_names";
+ /**
+ * 本 session 中已成功调用过 {@code read_exp} 的 experience id 集合;
+ * {@code read_exp_doc} 的 session gate 依赖该状态。
+ *
+ *
类型:List<String>
+ */
+ public static final String EXPERIENCE_READ_EXP_IDS = "experience_read_exp_ids";
+
// ==================== Session级别代码存储 ====================
/**
diff --git a/assistant-agent-core/pom.xml b/assistant-agent-core/pom.xml
index 12c89a11..f650f713 100644
--- a/assistant-agent-core/pom.xml
+++ b/assistant-agent-core/pom.xml
@@ -6,7 +6,7 @@
com.alibaba.agent.assistant
assistant-agent
- 0.2.5
+ 0.2.6
assistant-agent-core
diff --git a/assistant-agent-core/src/main/java/com/alibaba/assistant/agent/core/executor/GraalCodeExecutor.java b/assistant-agent-core/src/main/java/com/alibaba/assistant/agent/core/executor/GraalCodeExecutor.java
index c0bf5f8d..dd078583 100644
--- a/assistant-agent-core/src/main/java/com/alibaba/assistant/agent/core/executor/GraalCodeExecutor.java
+++ b/assistant-agent-core/src/main/java/com/alibaba/assistant/agent/core/executor/GraalCodeExecutor.java
@@ -471,15 +471,7 @@ private String toPythonLiteral(Object value) {
return "None";
}
if (value instanceof String) {
- // 转义特殊字符并使用三引号处理多行字符串
- String str = (String) value;
- if (str.contains("\n") || str.contains("\"") || str.contains("'")) {
- // 使用三引号,转义其中的三引号
- String escaped = str.replace("\\", "\\\\").replace("\"\"\"", "\\\"\\\"\\\"");
- return "\"\"\"" + escaped + "\"\"\"";
- } else {
- return "\"" + str.replace("\\", "\\\\").replace("\"", "\\\"") + "\"";
- }
+ return toPythonStringLiteral((String) value);
}
if (value instanceof Number) {
return value.toString();
@@ -515,6 +507,22 @@ private String toPythonLiteral(Object value) {
return convertComplexObjectToPythonLiteral(value);
}
+ static String toPythonStringLiteral(String value) {
+ if (value == null) {
+ return "None";
+ }
+ try {
+ return JSON_MAPPER.writeValueAsString(value);
+ } catch (JsonProcessingException e) {
+ logger.warn("GraalCodeExecutor#toPythonStringLiteral - reason=JSON字符串序列化失败, error={}", e.getMessage());
+ return "\"" + value.replace("\\", "\\\\")
+ .replace("\"", "\\\"")
+ .replace("\r", "\\r")
+ .replace("\n", "\\n")
+ .replace("\t", "\\t") + "\"";
+ }
+ }
+
/**
* 将复杂对象转换为 Python 字面量
*
diff --git a/assistant-agent-core/src/test/java/com/alibaba/assistant/agent/core/executor/GraalCodeExecutorStringLiteralTest.java b/assistant-agent-core/src/test/java/com/alibaba/assistant/agent/core/executor/GraalCodeExecutorStringLiteralTest.java
new file mode 100644
index 00000000..c582ea7a
--- /dev/null
+++ b/assistant-agent-core/src/test/java/com/alibaba/assistant/agent/core/executor/GraalCodeExecutorStringLiteralTest.java
@@ -0,0 +1,23 @@
+package com.alibaba.assistant.agent.core.executor;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+class GraalCodeExecutorStringLiteralTest {
+
+ @Test
+ void shouldEscapeTrailingDoubleQuoteSafely() {
+ String literal = GraalCodeExecutor.toPythonStringLiteral(
+ "帮我查询我参与的2026-03-06至2026-03-13期间进行中的发布计划\"");
+
+ assertEquals("\"帮我查询我参与的2026-03-06至2026-03-13期间进行中的发布计划\\\"\"", literal);
+ }
+
+ @Test
+ void shouldEscapeMultilineStringsSafely() {
+ String literal = GraalCodeExecutor.toPythonStringLiteral("用户原始需求: 第一行\n第二行\"结尾");
+
+ assertEquals("\"用户原始需求: 第一行\\n第二行\\\"结尾\"", literal);
+ }
+}
diff --git a/assistant-agent-evaluation/pom.xml b/assistant-agent-evaluation/pom.xml
index 69d67670..670dc282 100644
--- a/assistant-agent-evaluation/pom.xml
+++ b/assistant-agent-evaluation/pom.xml
@@ -6,7 +6,7 @@
com.alibaba.agent.assistant
assistant-agent
- 0.2.5
+ 0.2.6
assistant-agent-evaluation
@@ -73,4 +73,4 @@
-
\ No newline at end of file
+
diff --git a/assistant-agent-extensions/pom.xml b/assistant-agent-extensions/pom.xml
index f288a2b2..54e43525 100644
--- a/assistant-agent-extensions/pom.xml
+++ b/assistant-agent-extensions/pom.xml
@@ -6,7 +6,7 @@
com.alibaba.agent.assistant
assistant-agent
- 0.2.5
+ 0.2.6
assistant-agent-extensions
@@ -93,4 +93,4 @@
-
\ No newline at end of file
+
diff --git a/assistant-agent-extensions/src/main/java/com/alibaba/assistant/agent/extension/experience/config/ExperienceDisclosureAutoConfiguration.java b/assistant-agent-extensions/src/main/java/com/alibaba/assistant/agent/extension/experience/config/ExperienceDisclosureAutoConfiguration.java
index 7a549900..63bf1c26 100644
--- a/assistant-agent-extensions/src/main/java/com/alibaba/assistant/agent/extension/experience/config/ExperienceDisclosureAutoConfiguration.java
+++ b/assistant-agent-extensions/src/main/java/com/alibaba/assistant/agent/extension/experience/config/ExperienceDisclosureAutoConfiguration.java
@@ -85,8 +85,10 @@ public ExperiencePrefetchHook experiencePrefetchHook(ExperienceDisclosureService
@ConditionalOnMissingBean
public ExperienceRuntimeModelInterceptor experienceRuntimeModelInterceptor(ExperienceDisclosureService service,
ExperienceDisclosureContextResolver contextResolver,
- ExperienceToolInvocationClassifier classifier) {
- return new ExperienceRuntimeModelInterceptor(service, contextResolver, classifier);
+ ExperienceToolInvocationClassifier classifier,
+ ExperienceExtensionProperties properties) {
+ return new ExperienceRuntimeModelInterceptor(service, contextResolver, classifier,
+ properties.getReadExpDocMaxPaths());
}
/**
diff --git a/assistant-agent-extensions/src/main/java/com/alibaba/assistant/agent/extension/experience/config/ExperienceExtensionProperties.java b/assistant-agent-extensions/src/main/java/com/alibaba/assistant/agent/extension/experience/config/ExperienceExtensionProperties.java
index b6ad79c8..9ea2a035 100644
--- a/assistant-agent-extensions/src/main/java/com/alibaba/assistant/agent/extension/experience/config/ExperienceExtensionProperties.java
+++ b/assistant-agent-extensions/src/main/java/com/alibaba/assistant/agent/extension/experience/config/ExperienceExtensionProperties.java
@@ -74,6 +74,11 @@ public class ExperienceExtensionProperties {
*/
private int maxContentLength = 2000;
+ /**
+ * {@code read_exp_doc} 单次允许读取的路径数量上限。
+ */
+ private int readExpDocMaxPaths = 6;
+
/**
* 内存实现相关配置
*/
@@ -270,6 +275,14 @@ public void setMaxContentLength(int maxContentLength) {
this.maxContentLength = maxContentLength;
}
+ public int getReadExpDocMaxPaths() {
+ return readExpDocMaxPaths;
+ }
+
+ public void setReadExpDocMaxPaths(int readExpDocMaxPaths) {
+ this.readExpDocMaxPaths = readExpDocMaxPaths;
+ }
+
public InMemoryConfig getInMemory() {
return inMemory;
}
diff --git a/assistant-agent-extensions/src/main/java/com/alibaba/assistant/agent/extension/experience/disclosure/ExperienceDisclosurePayloads.java b/assistant-agent-extensions/src/main/java/com/alibaba/assistant/agent/extension/experience/disclosure/ExperienceDisclosurePayloads.java
index e76e9130..178c0241 100644
--- a/assistant-agent-extensions/src/main/java/com/alibaba/assistant/agent/extension/experience/disclosure/ExperienceDisclosurePayloads.java
+++ b/assistant-agent-extensions/src/main/java/com/alibaba/assistant/agent/extension/experience/disclosure/ExperienceDisclosurePayloads.java
@@ -353,6 +353,121 @@ public void setId(String id) {
}
}
+ public static class ReferenceManifestEntry implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ private String path;
+ private String mediaType;
+ private String description;
+ private Long size;
+
+ public String getPath() { return path; }
+ public void setPath(String path) { this.path = path; }
+ public String getMediaType() { return mediaType; }
+ public void setMediaType(String mediaType) { this.mediaType = mediaType; }
+ public String getDescription() { return description; }
+ public void setDescription(String description) { this.description = description; }
+ public Long getSize() { return size; }
+ public void setSize(Long size) { this.size = size; }
+ }
+
+ public static class AssetManifestEntry implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ private String path;
+ private String mediaType;
+ private String role;
+ private String description;
+ private Long size;
+
+ public String getPath() { return path; }
+ public void setPath(String path) { this.path = path; }
+ public String getMediaType() { return mediaType; }
+ public void setMediaType(String mediaType) { this.mediaType = mediaType; }
+ public String getRole() { return role; }
+ public void setRole(String role) { this.role = role; }
+ public String getDescription() { return description; }
+ public void setDescription(String description) { this.description = description; }
+ public Long getSize() { return size; }
+ public void setSize(Long size) { this.size = size; }
+ }
+
+ public static class ReadExpDocRequest implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ private String id;
+ private List paths = new ArrayList<>();
+
+ public String getId() { return id; }
+ public void setId(String id) { this.id = id; }
+ public List getPaths() { return paths; }
+ public void setPaths(List paths) { this.paths = paths != null ? paths : new ArrayList<>(); }
+ }
+
+ public static class ReadExpDocResponse implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ private String id;
+ private List documents = new ArrayList<>();
+ /** Non-fatal per-path errors (e.g., "path is an asset"); empty on success. */
+ private List errors = new ArrayList<>();
+
+ public String getId() { return id; }
+ public void setId(String id) { this.id = id; }
+ public List getDocuments() { return documents; }
+ public void setDocuments(List documents) {
+ this.documents = documents != null ? documents : new ArrayList<>();
+ }
+ public List getErrors() { return errors; }
+ public void setErrors(List errors) {
+ this.errors = errors != null ? errors : new ArrayList<>();
+ }
+ }
+
+ public static class ReadExpDocument implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ private String path;
+ private String mediaType;
+ private String content;
+
+ public String getPath() { return path; }
+ public void setPath(String path) { this.path = path; }
+ public String getMediaType() { return mediaType; }
+ public void setMediaType(String mediaType) { this.mediaType = mediaType; }
+ public String getContent() { return content; }
+ public void setContent(String content) { this.content = content; }
+ }
+
+ public static class ReadExpDocError implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ private String path;
+ private String reason;
+
+ public ReadExpDocError() {}
+ public ReadExpDocError(String path, String reason) {
+ this.path = path;
+ this.reason = reason;
+ }
+ public String getPath() { return path; }
+ public void setPath(String path) { this.path = path; }
+ public String getReason() { return reason; }
+ public void setReason(String reason) { this.reason = reason; }
+ }
+
public static class ReadExpResponse implements Serializable {
@Serial
@@ -371,6 +486,24 @@ public static class ReadExpResponse implements Serializable {
private ToolInvocationPath toolInvocationPath;
private String callableToolName;
private ExperienceArtifact artifact;
+ private List referenceManifest = new ArrayList<>();
+ private List assetManifest = new ArrayList<>();
+
+ public List getReferenceManifest() {
+ return referenceManifest;
+ }
+
+ public void setReferenceManifest(List referenceManifest) {
+ this.referenceManifest = referenceManifest != null ? referenceManifest : new ArrayList<>();
+ }
+
+ public List getAssetManifest() {
+ return assetManifest;
+ }
+
+ public void setAssetManifest(List assetManifest) {
+ this.assetManifest = assetManifest != null ? assetManifest : new ArrayList<>();
+ }
public boolean isFound() {
return found;
diff --git a/assistant-agent-extensions/src/main/java/com/alibaba/assistant/agent/extension/experience/disclosure/ExperienceDisclosurePromptContributor.java b/assistant-agent-extensions/src/main/java/com/alibaba/assistant/agent/extension/experience/disclosure/ExperienceDisclosurePromptContributor.java
index 5a40717c..c5eac2e2 100644
--- a/assistant-agent-extensions/src/main/java/com/alibaba/assistant/agent/extension/experience/disclosure/ExperienceDisclosurePromptContributor.java
+++ b/assistant-agent-extensions/src/main/java/com/alibaba/assistant/agent/extension/experience/disclosure/ExperienceDisclosurePromptContributor.java
@@ -126,6 +126,9 @@ protected String buildPrompt(GroupedExperienceCandidates candidates,
protected void appendGuidanceText(StringBuilder sb) {
sb.append("经验分为三类:COMMON 用于解释概念/产品术语,REACT 用于提供流程与策略参考,TOOL 用于能力判断、工具选择与调用路径判断。\n");
sb.append("披露方式分为两种:`DIRECT` 表示内容已满足高置信短文本条件,可直接利用;`PROGRESSIVE` 表示当前只给候选卡,需要完整正文时调用 `read_exp`。\n");
+ sb.append("渐进披露分三层:L1 候选卡(本块内容) → L2 `read_exp(id)` 返回 content + referenceManifest + assetManifest → L3 `read_exp_doc(id, paths)` 仅按需读取 referenceManifest 中的某些参考文档完整内容。\n");
+ sb.append("调用 `read_exp_doc` 前必须先 `read_exp(id)`,否则会返回错误:\"请先 read_exp(),再 read_exp_doc\";单次最多读取若干个路径,超出会被截断。\n");
+ sb.append("`read_exp_doc` 仅能读取 referenceManifest 列出的文档;assetManifest 列出的脚本/资源文件不可通过 `read_exp_doc` 访问 —— 它们已被预先物化到沙箱 `/workspace/` 下,仅在沙箱内通过工具执行(cat/python/bash 等)读取。\n");
sb.append("优先复用已披露的 DIRECT 内容,再从候选中选择 id;只有当前候选明显不足或缺少方向时,再调用 `search_exp`。\n");
sb.append("TOOL 候选如果标记为 `REACT_DIRECT`,表示它具备被直接作为 React function call 调用的资格;");
sb.append("但只有在该单个工具即可直接完成任务时,才优先 direct call。");
diff --git a/assistant-agent-extensions/src/main/java/com/alibaba/assistant/agent/extension/experience/disclosure/ExperienceDisclosureService.java b/assistant-agent-extensions/src/main/java/com/alibaba/assistant/agent/extension/experience/disclosure/ExperienceDisclosureService.java
index 0ecc5a10..65cedefa 100644
--- a/assistant-agent-extensions/src/main/java/com/alibaba/assistant/agent/extension/experience/disclosure/ExperienceDisclosureService.java
+++ b/assistant-agent-extensions/src/main/java/com/alibaba/assistant/agent/extension/experience/disclosure/ExperienceDisclosureService.java
@@ -1,24 +1,33 @@
package com.alibaba.assistant.agent.extension.experience.disclosure;
import com.alibaba.assistant.agent.extension.experience.config.ExperienceExtensionProperties;
-import com.alibaba.assistant.agent.extension.experience.disclosure.ExperienceDisclosurePayloads.ExperienceCandidateCard;
+import com.alibaba.assistant.agent.extension.experience.disclosure.ExperienceDisclosurePayloads.AssetManifestEntry;
import com.alibaba.assistant.agent.extension.experience.disclosure.ExperienceDisclosurePayloads.DirectExperienceGrounding;
+import com.alibaba.assistant.agent.extension.experience.disclosure.ExperienceDisclosurePayloads.ExperienceCandidateCard;
import com.alibaba.assistant.agent.extension.experience.disclosure.ExperienceDisclosurePayloads.GroupedExperienceCandidates;
import com.alibaba.assistant.agent.extension.experience.disclosure.ExperienceDisclosurePayloads.PrefetchedExperienceSnapshot;
+import com.alibaba.assistant.agent.extension.experience.disclosure.ExperienceDisclosurePayloads.ReadExpDocError;
+import com.alibaba.assistant.agent.extension.experience.disclosure.ExperienceDisclosurePayloads.ReadExpDocResponse;
+import com.alibaba.assistant.agent.extension.experience.disclosure.ExperienceDisclosurePayloads.ReadExpDocument;
import com.alibaba.assistant.agent.extension.experience.disclosure.ExperienceDisclosurePayloads.ReadExpResponse;
+import com.alibaba.assistant.agent.extension.experience.disclosure.ExperienceDisclosurePayloads.ReferenceManifestEntry;
import com.alibaba.assistant.agent.extension.experience.disclosure.ExperienceDisclosurePayloads.SearchExpResponse;
+import com.alibaba.assistant.agent.extension.experience.model.AssetEntry;
import com.alibaba.assistant.agent.extension.experience.model.DisclosureStrategy;
import com.alibaba.assistant.agent.extension.experience.model.Experience;
import com.alibaba.assistant.agent.extension.experience.model.ExperienceQuery;
import com.alibaba.assistant.agent.extension.experience.model.ExperienceQueryContext;
import com.alibaba.assistant.agent.extension.experience.model.ExperienceType;
+import com.alibaba.assistant.agent.extension.experience.model.ReferenceEntry;
import com.alibaba.assistant.agent.extension.experience.spi.ExperienceProvider;
import com.alibaba.assistant.agent.extension.experience.spi.ExperienceRepository;
import org.springframework.util.StringUtils;
import java.util.ArrayList;
+import java.util.LinkedHashSet;
import java.util.List;
import java.util.Optional;
+import java.util.Set;
import static com.alibaba.assistant.agent.extension.experience.disclosure.ExperienceDisclosurePayloads.PrefetchStatus;
@@ -107,6 +116,8 @@ public ReadExpResponse read(String id) {
response.setAssociatedTools(new ArrayList<>(experience.getAssociatedTools()));
response.setRelatedExperiences(new ArrayList<>(experience.getRelatedExperiences()));
response.setArtifact(experience.getArtifact());
+ response.setReferenceManifest(buildReferenceManifest(experience));
+ response.setAssetManifest(buildAssetManifest(experience));
if (experience.getType() == ExperienceType.TOOL) {
response.setToolInvocationPath(toolInvocationClassifier.classify(experience));
@@ -115,6 +126,114 @@ public ReadExpResponse read(String id) {
return response;
}
+ /**
+ * L3 渐进披露:为 {@code read_exp_doc} 工具提供 reference 内容读取。
+ *
+ * 仅接受 {@link Experience#getReferences()} 中声明的路径;asset 路径会被拒绝,
+ * 提示调用方在沙箱内以 {@code /workspace/} 读取。
+ *
+ * @param id 经验 ID
+ * @param paths 本次希望读取的路径列表(最多 {@code maxPaths} 个)
+ * @return 包含已命中文档及错误列表的响应
+ */
+ public ReadExpDocResponse readDocs(String id, List paths, int maxPaths) {
+ ReadExpDocResponse response = new ReadExpDocResponse();
+ response.setId(id);
+ if (!StringUtils.hasText(id) || paths == null || paths.isEmpty()) {
+ response.getErrors().add(new ReadExpDocError(null, "id 和 paths 均为必填"));
+ return response;
+ }
+ if (maxPaths > 0 && paths.size() > maxPaths) {
+ response.getErrors().add(new ReadExpDocError(null,
+ "paths 数量超过上限 " + maxPaths + ";请分多次调用"));
+ return response;
+ }
+ Optional opt = experienceRepository.findById(id);
+ if (opt.isEmpty()) {
+ response.getErrors().add(new ReadExpDocError(null, "Experience not found: " + id));
+ return response;
+ }
+ Experience experience = opt.get();
+ Set refPaths = new LinkedHashSet<>();
+ if (experience.getReferences() != null) {
+ for (ReferenceEntry r : experience.getReferences()) {
+ if (r.getPath() != null) {
+ refPaths.add(r.getPath());
+ }
+ }
+ }
+ Set assetPaths = new LinkedHashSet<>();
+ if (experience.getAssets() != null) {
+ for (AssetEntry a : experience.getAssets()) {
+ if (a.getPath() != null) {
+ assetPaths.add(a.getPath());
+ }
+ }
+ }
+ for (String requested : paths) {
+ if (requested == null || requested.isBlank()) {
+ response.getErrors().add(new ReadExpDocError(requested, "path 为空"));
+ continue;
+ }
+ if (assetPaths.contains(requested)) {
+ response.getErrors().add(new ReadExpDocError(requested,
+ "path is an asset; access it inside the sandbox workspace instead"));
+ continue;
+ }
+ if (!refPaths.contains(requested)) {
+ response.getErrors().add(new ReadExpDocError(requested,
+ "unknown path; available references: " + String.join(", ", refPaths)));
+ continue;
+ }
+ ReferenceEntry ref = experience.getReferences().stream()
+ .filter(r -> requested.equals(r.getPath()))
+ .findFirst()
+ .orElse(null);
+ if (ref == null) {
+ continue;
+ }
+ ReadExpDocument doc = new ReadExpDocument();
+ doc.setPath(ref.getPath());
+ doc.setMediaType(ref.getMediaType());
+ doc.setContent(ref.getContent());
+ response.getDocuments().add(doc);
+ }
+ return response;
+ }
+
+ private List buildReferenceManifest(Experience experience) {
+ List out = new ArrayList<>();
+ if (experience.getReferences() == null) {
+ return out;
+ }
+ for (ReferenceEntry r : experience.getReferences()) {
+ ReferenceManifestEntry m = new ReferenceManifestEntry();
+ m.setPath(r.getPath());
+ m.setMediaType(r.getMediaType());
+ m.setDescription(r.getDescription());
+ m.setSize(r.getSize());
+ out.add(m);
+ }
+ return out;
+ }
+
+ private List buildAssetManifest(Experience experience) {
+ List out = new ArrayList<>();
+ if (experience.getAssets() == null) {
+ return out;
+ }
+ for (AssetEntry a : experience.getAssets()) {
+ AssetManifestEntry m = new AssetManifestEntry();
+ m.setPath(a.getPath());
+ m.setMediaType(a.getMediaType());
+ m.setRole(a.getRole());
+ m.setDescription(a.getDescription());
+ m.setSize(a.getSize());
+ out.add(m);
+ }
+ return out;
+ }
+
private GroupedExperienceCandidates searchGrouped(List commonExperiences,
List reactExperiences,
List toolExperiences) {
diff --git a/assistant-agent-extensions/src/main/java/com/alibaba/assistant/agent/extension/experience/disclosure/ExperienceRuntimeModelInterceptor.java b/assistant-agent-extensions/src/main/java/com/alibaba/assistant/agent/extension/experience/disclosure/ExperienceRuntimeModelInterceptor.java
index 047ead0d..71cdad24 100644
--- a/assistant-agent-extensions/src/main/java/com/alibaba/assistant/agent/extension/experience/disclosure/ExperienceRuntimeModelInterceptor.java
+++ b/assistant-agent-extensions/src/main/java/com/alibaba/assistant/agent/extension/experience/disclosure/ExperienceRuntimeModelInterceptor.java
@@ -1,6 +1,8 @@
package com.alibaba.assistant.agent.extension.experience.disclosure;
import com.alibaba.assistant.agent.common.constant.CodeactStateKeys;
+import com.alibaba.assistant.agent.extension.experience.disclosure.ExperienceDisclosurePayloads.ReadExpDocRequest;
+import com.alibaba.assistant.agent.extension.experience.disclosure.ExperienceDisclosurePayloads.ReadExpDocResponse;
import com.alibaba.assistant.agent.extension.experience.disclosure.ExperienceDisclosurePayloads.ReadExpRequest;
import com.alibaba.assistant.agent.extension.experience.disclosure.ExperienceDisclosurePayloads.ReadExpResponse;
import com.alibaba.assistant.agent.extension.experience.disclosure.ExperienceDisclosurePayloads.SearchExpRequest;
@@ -40,19 +42,29 @@ public class ExperienceRuntimeModelInterceptor extends ModelInterceptor {
public static final String SEARCH_EXP_TOOL_NAME = "search_exp";
public static final String READ_EXP_TOOL_NAME = "read_exp";
+ public static final String READ_EXP_DOC_TOOL_NAME = "read_exp_doc";
private final ExperienceDisclosureService experienceDisclosureService;
private final ExperienceDisclosureContextResolver contextResolver;
private final ExperienceToolInvocationClassifier toolInvocationClassifier;
+ private final int readExpDocMaxPaths;
private final List tools;
public ExperienceRuntimeModelInterceptor(ExperienceDisclosureService experienceDisclosureService,
ExperienceDisclosureContextResolver contextResolver,
ExperienceToolInvocationClassifier toolInvocationClassifier) {
+ this(experienceDisclosureService, contextResolver, toolInvocationClassifier, 3);
+ }
+
+ public ExperienceRuntimeModelInterceptor(ExperienceDisclosureService experienceDisclosureService,
+ ExperienceDisclosureContextResolver contextResolver,
+ ExperienceToolInvocationClassifier toolInvocationClassifier,
+ int readExpDocMaxPaths) {
this.experienceDisclosureService = experienceDisclosureService;
this.contextResolver = contextResolver;
this.toolInvocationClassifier = toolInvocationClassifier;
- this.tools = List.of(buildSearchTool(), buildReadTool());
+ this.readExpDocMaxPaths = readExpDocMaxPaths > 0 ? readExpDocMaxPaths : 6;
+ this.tools = List.of(buildSearchTool(), buildReadTool(), buildReadDocTool());
}
@Override
@@ -118,6 +130,45 @@ private ToolCallback buildReadTool() {
.build();
}
+ private ToolCallback buildReadDocTool() {
+ BiFunction function = (request, toolContext) -> {
+ String id = request != null ? request.getId() : null;
+ List paths = request != null ? request.getPaths() : List.of();
+ OverAllState state = extractState(toolContext);
+ if (!hasPriorReadExp(state, id)) {
+ ReadExpDocResponse err = new ReadExpDocResponse();
+ err.setId(id);
+ err.getErrors().add(new ExperienceDisclosurePayloads.ReadExpDocError(null,
+ "请先 read_exp(" + id + "),再 read_exp_doc"));
+ return JSON.toJSONString(err);
+ }
+ ReadExpDocResponse response = experienceDisclosureService.readDocs(id, paths, readExpDocMaxPaths);
+ return JSON.toJSONString(response);
+ };
+ return FunctionToolCallback.builder(READ_EXP_DOC_TOOL_NAME, function)
+ .description("读取指定经验 references 列表中某些文档的完整内容(L3 渐进披露)。需要先 read_exp(id)。"
+ + "单次最多读取 " + readExpDocMaxPaths + " 个路径,超过会整次失败;"
+ + "请尽量把本轮需要的参考文档合并到一次调用中,避免反复多次调用。"
+ + "不能用于 asset 路径;asset 仅在沙箱 /workspace 下可访问。")
+ .inputType(ReadExpDocRequest.class)
+ .build();
+ }
+
+ private boolean hasPriorReadExp(OverAllState state, String id) {
+ if (state == null || id == null || id.isBlank()) {
+ return false;
+ }
+ Object value = state.value(CodeactStateKeys.EXPERIENCE_READ_EXP_IDS).orElse(null);
+ if (value instanceof List> list) {
+ for (Object item : list) {
+ if (id.equals(String.valueOf(item))) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
private Set extractAllowedDirectToolNames(Map context) {
if (context == null) {
return Set.of();
diff --git a/assistant-agent-extensions/src/main/java/com/alibaba/assistant/agent/extension/experience/disclosure/ExperienceRuntimeToolStateInterceptor.java b/assistant-agent-extensions/src/main/java/com/alibaba/assistant/agent/extension/experience/disclosure/ExperienceRuntimeToolStateInterceptor.java
index b11b4262..706b0144 100644
--- a/assistant-agent-extensions/src/main/java/com/alibaba/assistant/agent/extension/experience/disclosure/ExperienceRuntimeToolStateInterceptor.java
+++ b/assistant-agent-extensions/src/main/java/com/alibaba/assistant/agent/extension/experience/disclosure/ExperienceRuntimeToolStateInterceptor.java
@@ -55,6 +55,23 @@ public ToolCallResponse interceptToolCall(ToolCallRequest request, ToolCallHandl
return response;
}
+ private void recordReadExpId(OverAllState state, String id) {
+ if (state == null || id == null || id.isBlank()) {
+ return;
+ }
+ LinkedHashSet ids = new LinkedHashSet<>();
+ Object existing = state.value(CodeactStateKeys.EXPERIENCE_READ_EXP_IDS).orElse(null);
+ if (existing instanceof List> list) {
+ for (Object item : list) {
+ if (item != null) {
+ ids.add(String.valueOf(item));
+ }
+ }
+ }
+ ids.add(id);
+ state.updateState(Map.of(CodeactStateKeys.EXPERIENCE_READ_EXP_IDS, new ArrayList<>(ids)));
+ }
+
private void mergeDirectToolNames(OverAllState state, String json, String toolName) {
LinkedHashSet merged = new LinkedHashSet<>();
Object existing = state.value(CodeactStateKeys.EXPERIENCE_ALLOWED_REACT_TOOL_NAMES).orElse(null);
@@ -120,6 +137,7 @@ private void maybeCacheReadDetail(OverAllState state, String json, String toolNa
}
cache.put(response.getId(), response);
state.updateState(Map.of(CodeactStateKeys.EXPERIENCE_DETAIL_CACHE, cache));
+ recordReadExpId(state, response.getId());
}
private void mergeDirectGroundings(OverAllState state, String json, String toolName) {
diff --git a/assistant-agent-extensions/src/main/java/com/alibaba/assistant/agent/extension/experience/model/AssetEntry.java b/assistant-agent-extensions/src/main/java/com/alibaba/assistant/agent/extension/experience/model/AssetEntry.java
new file mode 100644
index 00000000..8d56fe92
--- /dev/null
+++ b/assistant-agent-extensions/src/main/java/com/alibaba/assistant/agent/extension/experience/model/AssetEntry.java
@@ -0,0 +1,128 @@
+package com.alibaba.assistant.agent.extension.experience.model;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 经验中仅在沙箱内被使用的附件条目(脚本、静态资源、评估数据等)。
+ *
+ * {@link Experience#getAssets()} 中的条目 不会 被 {@code read_exp_doc}
+ * 披露给模型;CLI 执行路径会把它们写入沙箱 {@code /workspace/},由脚本/命令
+ * 自行消费。{@code read_exp} 只返回 manifest(不含 {@code content})。
+ *
+ * 典型来源:{@code scripts/**}、{@code assets/**}、{@code evals/**}、
+ * {@code package.json} 等。
+ */
+public class AssetEntry implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * skill 包内的相对路径,如 {@code scripts/analyze.py}。
+ */
+ private String path;
+
+ /**
+ * MIME 类型。
+ */
+ private String mediaType;
+
+ /**
+ * 角色分类:{@code script} / {@code asset} / {@code eval} / {@code metadata}。
+ */
+ private String role;
+
+ /**
+ * 简要描述(由 H1 / LLM 总结 / fallback 产生)。
+ */
+ private String description;
+
+ /**
+ * 原始字节数。
+ */
+ private Long size;
+
+ /**
+ * 内容;文本类以 UTF-8 字符串存储,二进制类以 Base64 编码。null 表示需要通过
+ * {@link #contentRef} 从外部存储拉取。
+ */
+ private String content;
+
+ /**
+ * 外部存储(如 OSS)引用;目前预留字段,未启用。
+ */
+ private String contentRef;
+
+ /**
+ * 内容 SHA-256 摘要,便于缓存 description。
+ */
+ private String contentHash;
+
+ public AssetEntry() {
+ }
+
+ public String getPath() {
+ return path;
+ }
+
+ public void setPath(String path) {
+ this.path = path;
+ }
+
+ public String getMediaType() {
+ return mediaType;
+ }
+
+ public void setMediaType(String mediaType) {
+ this.mediaType = mediaType;
+ }
+
+ public String getRole() {
+ return role;
+ }
+
+ public void setRole(String role) {
+ this.role = role;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ public Long getSize() {
+ return size;
+ }
+
+ public void setSize(Long size) {
+ this.size = size;
+ }
+
+ public String getContent() {
+ return content;
+ }
+
+ public void setContent(String content) {
+ this.content = content;
+ }
+
+ public String getContentRef() {
+ return contentRef;
+ }
+
+ public void setContentRef(String contentRef) {
+ this.contentRef = contentRef;
+ }
+
+ public String getContentHash() {
+ return contentHash;
+ }
+
+ public void setContentHash(String contentHash) {
+ this.contentHash = contentHash;
+ }
+}
diff --git a/assistant-agent-extensions/src/main/java/com/alibaba/assistant/agent/extension/experience/model/CliRuntimeConstants.java b/assistant-agent-extensions/src/main/java/com/alibaba/assistant/agent/extension/experience/model/CliRuntimeConstants.java
new file mode 100644
index 00000000..9293fd4f
--- /dev/null
+++ b/assistant-agent-extensions/src/main/java/com/alibaba/assistant/agent/extension/experience/model/CliRuntimeConstants.java
@@ -0,0 +1,46 @@
+package com.alibaba.assistant.agent.extension.experience.model;
+
+import java.util.List;
+
+public final class CliRuntimeConstants {
+
+ public static final String SOURCE_CLI = "cli";
+ public static final String EXECUTION_MODE_SANDBOX = "SANDBOX";
+ public static final String AUTH_PROFILE_USER_TOKEN_BROKER = "USER_TOKEN_BROKER";
+ public static final String AUTH_PROFILE_SERVER_STATIC_ENV = "SERVER_STATIC_ENV";
+ public static final String SANDBOX_TEMPLATE_CODE_INTERPRETER = "code-interpreter";
+ public static final String OUTPUT_FORMAT_TEXT = "text";
+
+ /** 每个 CLI provider 对应的唯一 TOOL 名称前缀/后缀。结果形如 {@code cli__tool}。 */
+ public static final String TOOL_NAME_PREFIX = "cli_";
+ public static final String TOOL_NAME_SUFFIX = "_tool";
+
+ /** 通用 TOOL experience id 前缀:{@code tool-cli-}。 */
+ public static final String TOOL_ID_PREFIX = "tool-cli-";
+
+ /** 归类 CLI 工具的 targetClassName(供 CodeactTool 使用)。 */
+ public static final String TOOL_GROUP_CLASS_NAME = "cli_tools";
+
+ /** 工具的唯一入参名,承载模型侧提交的 shell 命令。 */
+ public static final String COMMAND_PARAM = "command";
+
+ /** 空闲沙箱淘汰时间(秒)的配置键。 */
+ public static final String IDLE_TIMEOUT_PROPERTY = "aone.tool.cli.sandbox.idle-timeout-seconds";
+ public static final long DEFAULT_IDLE_TIMEOUT_SECONDS = 300L;
+
+ /** 默认允许作为管道下游的可执行文件白名单。 */
+ public static final List DEFAULT_PIPE_ALLOWLIST = List.of(
+ "jq", "grep", "head", "tail", "sed", "awk", "sort", "uniq", "wc", "tr", "cut"
+ );
+
+ public static String toolName(String providerId) {
+ return TOOL_NAME_PREFIX + providerId + TOOL_NAME_SUFFIX;
+ }
+
+ public static String toolExperienceId(String providerId) {
+ return TOOL_ID_PREFIX + providerId;
+ }
+
+ private CliRuntimeConstants() {
+ }
+}
diff --git a/assistant-agent-extensions/src/main/java/com/alibaba/assistant/agent/extension/experience/model/Experience.java b/assistant-agent-extensions/src/main/java/com/alibaba/assistant/agent/extension/experience/model/Experience.java
index a1d7a73b..41f2827b 100644
--- a/assistant-agent-extensions/src/main/java/com/alibaba/assistant/agent/extension/experience/model/Experience.java
+++ b/assistant-agent-extensions/src/main/java/com/alibaba/assistant/agent/extension/experience/model/Experience.java
@@ -84,6 +84,16 @@ public class Experience {
*/
private ExperienceMetadata metadata;
+ /**
+ * 渐进披露第三层(L3)面向 LLM 的参考文档。仅通过 {@code read_exp_doc} 按路径读取。
+ */
+ private List references = new ArrayList<>();
+
+ /**
+ * 仅在沙箱内使用的附件(脚本/静态资源/评估数据)。不会通过 {@code read_exp_doc} 披露。
+ */
+ private List assets = new ArrayList<>();
+
public Experience() {
this.id = UUID.randomUUID().toString();
this.createdAt = Instant.now();
@@ -239,6 +249,22 @@ public void setMetadata(ExperienceMetadata metadata) {
this.metadata = metadata != null ? metadata : new ExperienceMetadata();
}
+ public List getReferences() {
+ return references;
+ }
+
+ public void setReferences(List references) {
+ this.references = references != null ? references : new ArrayList<>();
+ }
+
+ public List getAssets() {
+ return assets;
+ }
+
+ public void setAssets(List assets) {
+ this.assets = assets != null ? assets : new ArrayList<>();
+ }
+
/**
* 更新经验时自动更新时间戳
*/
diff --git a/assistant-agent-extensions/src/main/java/com/alibaba/assistant/agent/extension/experience/model/ExperienceArtifact.java b/assistant-agent-extensions/src/main/java/com/alibaba/assistant/agent/extension/experience/model/ExperienceArtifact.java
index 74589d5a..88db4b00 100644
--- a/assistant-agent-extensions/src/main/java/com/alibaba/assistant/agent/extension/experience/model/ExperienceArtifact.java
+++ b/assistant-agent-extensions/src/main/java/com/alibaba/assistant/agent/extension/experience/model/ExperienceArtifact.java
@@ -14,6 +14,9 @@
* REACT:可直接构造 AssistantMessage(toolCalls + 可选文本) 进入 tool 执行
* TOOL:提供工具连接信息和Schema,用于创建 CodeactTool
*
+ *
+ * 注意:skill package 的文件资源由顶层 {@link Experience#getReferences()} 与
+ * {@link Experience#getAssets()} 承载,artifact 不再保有 packageBundle 字段。
*/
public class ExperienceArtifact implements Serializable {
@@ -124,7 +127,7 @@ public static class ToolArtifact implements Serializable {
private static final long serialVersionUID = 1L;
/**
- * 工具来源类型:mcp / a2a / http
+ * 工具来源类型:mcp / a2a / http / cli
*/
private String source;
@@ -143,6 +146,33 @@ public static class ToolArtifact implements Serializable {
private String httpUrl;
private String httpBodyTemplate;
+ // --- CLI 运行时信息 ---
+ /**
+ * 该 TOOL 绑定的 CLI provider id
+ * 一个 provider 对应唯一的 TOOL experience({@code cli__tool})。
+ */
+ private String providerId;
+
+ /**
+ * 允许执行的命令前缀正则(必须以 ^ 锚定,例如 "^dbs($|\\s)")。
+ * 模型提交的 {@code command} 参数在分段后,首个 token 必须匹配该正则才会被执行。
+ */
+ private String commandAllowPattern;
+
+ /**
+ * 允许作为管道下游的外部可执行文件白名单(如 jq/grep 等)。
+ * 若为 {@code null} 或空,则仅允许没有管道的命令。
+ */
+ private java.util.List pipeAllowlist;
+
+ private String runnerImage;
+ private String sandboxTemplate;
+ private String executionMode;
+ private String authProfile;
+ private String authProvider;
+ private String loginCommandTemplate;
+ private String outputFormat;
+
/**
* 工具输入 Schema(JSON Schema 格式字符串)
*/
@@ -243,6 +273,86 @@ public void setHttpBodyTemplate(String httpBodyTemplate) {
this.httpBodyTemplate = httpBodyTemplate;
}
+ public String getRunnerImage() {
+ return runnerImage;
+ }
+
+ public void setRunnerImage(String runnerImage) {
+ this.runnerImage = runnerImage;
+ }
+
+ public String getSandboxTemplate() {
+ return sandboxTemplate;
+ }
+
+ public void setSandboxTemplate(String sandboxTemplate) {
+ this.sandboxTemplate = sandboxTemplate;
+ }
+
+ public String getExecutionMode() {
+ return executionMode;
+ }
+
+ public void setExecutionMode(String executionMode) {
+ this.executionMode = executionMode;
+ }
+
+ public String getAuthProfile() {
+ return authProfile;
+ }
+
+ public void setAuthProfile(String authProfile) {
+ this.authProfile = authProfile;
+ }
+
+ public String getAuthProvider() {
+ return authProvider;
+ }
+
+ public void setAuthProvider(String authProvider) {
+ this.authProvider = authProvider;
+ }
+
+ public String getLoginCommandTemplate() {
+ return loginCommandTemplate;
+ }
+
+ public void setLoginCommandTemplate(String loginCommandTemplate) {
+ this.loginCommandTemplate = loginCommandTemplate;
+ }
+
+ public String getProviderId() {
+ return providerId;
+ }
+
+ public void setProviderId(String providerId) {
+ this.providerId = providerId;
+ }
+
+ public String getCommandAllowPattern() {
+ return commandAllowPattern;
+ }
+
+ public void setCommandAllowPattern(String commandAllowPattern) {
+ this.commandAllowPattern = commandAllowPattern;
+ }
+
+ public java.util.List getPipeAllowlist() {
+ return pipeAllowlist;
+ }
+
+ public void setPipeAllowlist(java.util.List pipeAllowlist) {
+ this.pipeAllowlist = pipeAllowlist;
+ }
+
+ public String getOutputFormat() {
+ return outputFormat;
+ }
+
+ public void setOutputFormat(String outputFormat) {
+ this.outputFormat = outputFormat;
+ }
+
public String getInputSchema() {
return inputSchema;
}
@@ -275,5 +385,5 @@ public void setCodeactToolName(String codeactToolName) {
this.codeactToolName = codeactToolName;
}
}
-}
+}
diff --git a/assistant-agent-extensions/src/main/java/com/alibaba/assistant/agent/extension/experience/model/ReferenceEntry.java b/assistant-agent-extensions/src/main/java/com/alibaba/assistant/agent/extension/experience/model/ReferenceEntry.java
new file mode 100644
index 00000000..8e0367c6
--- /dev/null
+++ b/assistant-agent-extensions/src/main/java/com/alibaba/assistant/agent/extension/experience/model/ReferenceEntry.java
@@ -0,0 +1,101 @@
+package com.alibaba.assistant.agent.extension.experience.model;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 经验中渐进披露第三层(L3)可供模型读取的参考文档条目。
+ *
+ * {@link Experience#getReferences()} 中的条目在 {@code read_exp} 响应里仅以
+ * {@code path/mediaType/description/size} 这些字段暴露为 manifest;完整内容
+ * 仅能通过 {@code read_exp_doc} 按需读取。
+ *
+ *
典型来源:{@code references/**}、根目录的 {@code *.md}/{@code *.yaml}、
+ * agents 目录下的 markdown 等面向 LLM 的文档。
+ */
+public class ReferenceEntry implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * skill 包内的相对路径,如 {@code references/domains/session.md}。
+ */
+ private String path;
+
+ /**
+ * MIME 类型,如 {@code text/markdown}、{@code application/yaml}。
+ */
+ private String mediaType;
+
+ /**
+ * 文档描述(由 H1 标题 / frontmatter / LLM 总结 / 回退文案生成)。
+ */
+ private String description;
+
+ /**
+ * 文档正文(UTF-8 文本)。
+ */
+ private String content;
+
+ /**
+ * 正文的 SHA-256 摘要,用于复用 description 缓存。
+ */
+ private String contentHash;
+
+ /**
+ * 原始字节数。
+ */
+ private Long size;
+
+ public ReferenceEntry() {
+ }
+
+ public String getPath() {
+ return path;
+ }
+
+ public void setPath(String path) {
+ this.path = path;
+ }
+
+ public String getMediaType() {
+ return mediaType;
+ }
+
+ public void setMediaType(String mediaType) {
+ this.mediaType = mediaType;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ public String getContent() {
+ return content;
+ }
+
+ public void setContent(String content) {
+ this.content = content;
+ }
+
+ public String getContentHash() {
+ return contentHash;
+ }
+
+ public void setContentHash(String contentHash) {
+ this.contentHash = contentHash;
+ }
+
+ public Long getSize() {
+ return size;
+ }
+
+ public void setSize(Long size) {
+ this.size = size;
+ }
+}
diff --git a/assistant-agent-extensions/src/main/java/com/alibaba/assistant/agent/extension/reply/tools/BaseReplyCodeactTool.java b/assistant-agent-extensions/src/main/java/com/alibaba/assistant/agent/extension/reply/tools/BaseReplyCodeactTool.java
index c80030ed..11fc38c2 100644
--- a/assistant-agent-extensions/src/main/java/com/alibaba/assistant/agent/extension/reply/tools/BaseReplyCodeactTool.java
+++ b/assistant-agent-extensions/src/main/java/com/alibaba/assistant/agent/extension/reply/tools/BaseReplyCodeactTool.java
@@ -315,7 +315,9 @@ private CodeactToolMetadata buildCodeactMetadata() {
*/
private String buildInputSchemaFromParameterSchema() {
if (parameterSchema == null || parameterSchema.getParameters().isEmpty()) {
- return "{}";
+ // 必须返回合法 JSON Schema 对象, 否则部分模型提供方(如 idealab Anthropic 兼容层)会
+ // 拒 400: tools.N.custom.input_schema.type: Field required
+ return "{\"type\":\"object\",\"properties\":{}}";
}
Map schema = new LinkedHashMap<>();
diff --git a/assistant-agent-extensions/src/test/java/com/alibaba/assistant/agent/extension/experience/disclosure/ExperienceDisclosurePromptContributorTest.java b/assistant-agent-extensions/src/test/java/com/alibaba/assistant/agent/extension/experience/disclosure/ExperienceDisclosurePromptContributorTest.java
index 125fa17c..57225803 100644
--- a/assistant-agent-extensions/src/test/java/com/alibaba/assistant/agent/extension/experience/disclosure/ExperienceDisclosurePromptContributorTest.java
+++ b/assistant-agent-extensions/src/test/java/com/alibaba/assistant/agent/extension/experience/disclosure/ExperienceDisclosurePromptContributorTest.java
@@ -129,6 +129,11 @@ void contributeBuildsPromptWithCandidatesAndGroundings() {
// Verify guidance text (Chinese body with tool references)
assertTrue(text.contains("search_exp"));
assertTrue(text.contains("read_exp"));
+ assertTrue(text.contains("read_exp_doc"), "prompt must describe read_exp_doc");
+ assertTrue(text.contains("referenceManifest") || text.contains("manifest"),
+ "prompt must mention manifest");
+ assertTrue(text.contains("/workspace/") || text.contains("沙箱"),
+ "prompt must describe sandbox workspace for assets");
assertTrue(text.contains("PROGRESSIVE"));
assertTrue(text.contains("DIRECT"));
}
diff --git a/assistant-agent-management/pom.xml b/assistant-agent-management/pom.xml
index b1da0e69..cc6099c8 100644
--- a/assistant-agent-management/pom.xml
+++ b/assistant-agent-management/pom.xml
@@ -6,7 +6,7 @@
com.alibaba.agent.assistant
assistant-agent
- 0.2.5
+ 0.2.6
assistant-agent-management
@@ -45,5 +45,11 @@
com.fasterxml.jackson.core
jackson-databind
+
+
+ org.junit.jupiter
+ junit-jupiter
+ test
+
diff --git a/assistant-agent-management/src/main/java/com/alibaba/assistant/agent/management/config/ExperienceConsoleAutoConfiguration.java b/assistant-agent-management/src/main/java/com/alibaba/assistant/agent/management/config/ExperienceConsoleAutoConfiguration.java
index 45970cee..67c6d958 100644
--- a/assistant-agent-management/src/main/java/com/alibaba/assistant/agent/management/config/ExperienceConsoleAutoConfiguration.java
+++ b/assistant-agent-management/src/main/java/com/alibaba/assistant/agent/management/config/ExperienceConsoleAutoConfiguration.java
@@ -9,13 +9,18 @@
import com.alibaba.assistant.agent.management.controller.ToolSourceController;
import com.alibaba.assistant.agent.management.internal.InMemorySkillExchangeService;
import com.alibaba.assistant.agent.management.internal.InMemoryToolSourceBrowser;
+import com.alibaba.assistant.agent.management.internal.LlmReferenceSummarizer;
+import com.alibaba.assistant.agent.management.internal.NoopReferenceSummarizer;
import com.alibaba.assistant.agent.management.internal.RepositoryBackedExperienceManagementService;
import com.alibaba.assistant.agent.management.internal.SkillPackageParser;
import com.alibaba.assistant.agent.management.spi.ExperienceManagementService;
+import com.alibaba.assistant.agent.management.spi.ReferenceSummarizer;
import com.alibaba.assistant.agent.management.spi.SkillExchangeService;
import com.alibaba.assistant.agent.management.spi.TenantListProvider;
import com.alibaba.assistant.agent.management.spi.ToolSourceBrowser;
+import org.springframework.ai.chat.model.ChatModel;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
@@ -42,8 +47,9 @@ public class ExperienceConsoleAutoConfiguration {
@ConditionalOnMissingBean(ExperienceManagementService.class)
public ExperienceManagementService repositoryBackedExperienceManagementService(
ExperienceRepository repository,
- @Autowired(required = false) ExperienceToolInvocationClassifier toolInvocationClassifier) {
- return new RepositoryBackedExperienceManagementService(repository, toolInvocationClassifier);
+ @Autowired(required = false) ExperienceToolInvocationClassifier toolInvocationClassifier,
+ ReferenceSummarizer referenceSummarizer) {
+ return new RepositoryBackedExperienceManagementService(repository, toolInvocationClassifier, referenceSummarizer);
}
@Bean
@@ -54,8 +60,22 @@ public ToolSourceBrowser inMemoryToolSourceBrowser() {
@Bean
@ConditionalOnMissingBean(SkillExchangeService.class)
- public SkillExchangeService inMemorySkillExchangeService(ExperienceRepository repository) {
- return new InMemorySkillExchangeService(repository);
+ public SkillExchangeService inMemorySkillExchangeService(ExperienceRepository repository,
+ ReferenceSummarizer referenceSummarizer) {
+ return new InMemorySkillExchangeService(repository, referenceSummarizer);
+ }
+
+ @Bean
+ @ConditionalOnBean(ChatModel.class)
+ @ConditionalOnMissingBean(ReferenceSummarizer.class)
+ public ReferenceSummarizer llmReferenceSummarizer(ChatModel chatModel) {
+ return new LlmReferenceSummarizer(chatModel);
+ }
+
+ @Bean
+ @ConditionalOnMissingBean(ReferenceSummarizer.class)
+ public ReferenceSummarizer noopReferenceSummarizer() {
+ return new NoopReferenceSummarizer();
}
@Bean
diff --git a/assistant-agent-management/src/main/java/com/alibaba/assistant/agent/management/controller/ExperienceManagementController.java b/assistant-agent-management/src/main/java/com/alibaba/assistant/agent/management/controller/ExperienceManagementController.java
index a2c7dc05..026ad5a2 100644
--- a/assistant-agent-management/src/main/java/com/alibaba/assistant/agent/management/controller/ExperienceManagementController.java
+++ b/assistant-agent-management/src/main/java/com/alibaba/assistant/agent/management/controller/ExperienceManagementController.java
@@ -111,6 +111,45 @@ public ResponseEntity getById(@PathVariable("id") String id) {
return ResponseEntity.ok(vo);
}
+ /**
+ * 返回指定 asset 的原始内容(文本或 base64),供管理后台 UI 懒加载。
+ * 路径支持多级,如 /{id}/assets/scripts/cmd.sh。
+ */
+ @GetMapping("/{id}/assets/**")
+ public ResponseEntity