Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions assistant-agent-autoconfigure/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<parent>
<groupId>com.alibaba.agent.assistant</groupId>
<artifactId>assistant-agent</artifactId>
<version>0.2.5</version>
<version>0.2.6</version>
</parent>

<artifactId>assistant-agent-autoconfigure</artifactId>
Expand Down Expand Up @@ -88,4 +88,4 @@
</dependency>
</dependencies>

</project>
</project>
Original file line number Diff line number Diff line change
Expand Up @@ -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. 函数结构:
Expand Down
4 changes: 2 additions & 2 deletions assistant-agent-common/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<parent>
<groupId>com.alibaba.agent.assistant</groupId>
<artifactId>assistant-agent</artifactId>
<version>0.2.5</version>
<version>0.2.6</version>
</parent>

<artifactId>assistant-agent-common</artifactId>
Expand Down Expand Up @@ -43,4 +43,4 @@
</dependency>
</dependencies>

</project>
</project>
Original file line number Diff line number Diff line change
Expand Up @@ -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 依赖该状态。
*
* <p>类型:List&lt;String&gt;
*/
public static final String EXPERIENCE_READ_EXP_IDS = "experience_read_exp_ids";

// ==================== Session级别代码存储 ====================

/**
Expand Down
2 changes: 1 addition & 1 deletion assistant-agent-core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<parent>
<groupId>com.alibaba.agent.assistant</groupId>
<artifactId>assistant-agent</artifactId>
<version>0.2.5</version>
<version>0.2.6</version>
</parent>

<artifactId>assistant-agent-core</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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 字面量
*
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}
}
4 changes: 2 additions & 2 deletions assistant-agent-evaluation/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<parent>
<groupId>com.alibaba.agent.assistant</groupId>
<artifactId>assistant-agent</artifactId>
<version>0.2.5</version>
<version>0.2.6</version>
</parent>

<artifactId>assistant-agent-evaluation</artifactId>
Expand Down Expand Up @@ -73,4 +73,4 @@
</dependency>
</dependencies>

</project>
</project>
4 changes: 2 additions & 2 deletions assistant-agent-extensions/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<parent>
<groupId>com.alibaba.agent.assistant</groupId>
<artifactId>assistant-agent</artifactId>
<version>0.2.5</version>
<version>0.2.6</version>
</parent>

<artifactId>assistant-agent-extensions</artifactId>
Expand Down Expand Up @@ -93,4 +93,4 @@
</dependency>
</dependencies>

</project>
</project>
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,11 @@ public class ExperienceExtensionProperties {
*/
private int maxContentLength = 2000;

/**
* {@code read_exp_doc} 单次允许读取的路径数量上限。
*/
private int readExpDocMaxPaths = 6;

/**
* 内存实现相关配置
*/
Expand Down Expand Up @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<String> paths = new ArrayList<>();

public String getId() { return id; }
public void setId(String id) { this.id = id; }
public List<String> getPaths() { return paths; }
public void setPaths(List<String> 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<ReadExpDocument> documents = new ArrayList<>();
/** Non-fatal per-path errors (e.g., "path is an asset"); empty on success. */
private List<ReadExpDocError> errors = new ArrayList<>();

public String getId() { return id; }
public void setId(String id) { this.id = id; }
public List<ReadExpDocument> getDocuments() { return documents; }
public void setDocuments(List<ReadExpDocument> documents) {
this.documents = documents != null ? documents : new ArrayList<>();
}
public List<ReadExpDocError> getErrors() { return errors; }
public void setErrors(List<ReadExpDocError> 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
Expand All @@ -371,6 +486,24 @@ public static class ReadExpResponse implements Serializable {
private ToolInvocationPath toolInvocationPath;
private String callableToolName;
private ExperienceArtifact artifact;
private List<ReferenceManifestEntry> referenceManifest = new ArrayList<>();
private List<AssetManifestEntry> assetManifest = new ArrayList<>();

public List<ReferenceManifestEntry> getReferenceManifest() {
return referenceManifest;
}

public void setReferenceManifest(List<ReferenceManifestEntry> referenceManifest) {
this.referenceManifest = referenceManifest != null ? referenceManifest : new ArrayList<>();
}

public List<AssetManifestEntry> getAssetManifest() {
return assetManifest;
}

public void setAssetManifest(List<AssetManifestEntry> assetManifest) {
this.assetManifest = assetManifest != null ? assetManifest : new ArrayList<>();
}

public boolean isFound() {
return found;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(<id>),再 read_exp_doc\";单次最多读取若干个路径,超出会被截断。\n");
Copy link

Copilot AI Apr 27, 2026

Choose a reason for hiding this comment

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

Guidance text says when read_exp_doc exceeds the path limit it will be "截断" (truncated), but ExperienceDisclosureService#readDocs returns an error and no documents when paths.size() > maxPaths. Please align the prompt guidance with the actual tool behavior to avoid the model taking an incorrect call pattern.

Suggested change
sb.append("调用 `read_exp_doc` 前必须先 `read_exp(id)`,否则会返回错误:\"请先 read_exp(<id>),再 read_exp_doc\";单次最多读取若干个路径,超出会被截断\n");
sb.append("调用 `read_exp_doc` 前必须先 `read_exp(id)`,否则会返回错误:\"请先 read_exp(<id>),再 read_exp_doc\";单次最多读取若干个路径,超出会返回错误,且不会返回任何文档内容\n");

Copilot uses AI. Check for mistakes.
sb.append("`read_exp_doc` 仅能读取 referenceManifest 列出的文档;assetManifest 列出的脚本/资源文件不可通过 `read_exp_doc` 访问 —— 它们已被预先物化到沙箱 `/workspace/<path>` 下,仅在沙箱内通过工具执行(cat/python/bash 等)读取。\n");
sb.append("优先复用已披露的 DIRECT 内容,再从候选中选择 id;只有当前候选明显不足或缺少方向时,再调用 `search_exp`。\n");
sb.append("TOOL 候选如果标记为 `REACT_DIRECT`,表示它具备被直接作为 React function call 调用的资格;");
sb.append("但只有在该单个工具即可直接完成任务时,才优先 direct call。");
Expand Down
Loading
Loading