feat: 打通智能体图片媒体运行链路

- 增加稳定媒体引用解析与模型图片能力校验

- 修复图片 Data URI 与模型完整文本读取

- 补充媒体解析和消息兼容测试
This commit is contained in:
2026-07-17 19:45:37 +08:00
parent 66da0c9039
commit f057900f7a
19 changed files with 629 additions and 12 deletions

View File

@@ -5,7 +5,9 @@ import io.agentscope.core.message.*;
import java.time.Instant;
import java.time.OffsetDateTime;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Base64;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -15,6 +17,9 @@ import java.util.Map;
*/
public class AgentScopeMessageAdapter {
/** AgentScope URLSource 中用于持久化业务媒体引用的私有协议。 */
public static final String MEDIA_REFERENCE_SCHEME = "easyagents-media://";
/**
* 将运行时消息转换为 AgentScope 消息。
*
@@ -276,6 +281,11 @@ public class AgentScopeMessageAdapter {
.data(block.getData())
.build();
}
if (block.getReference() != null && !block.getReference().isBlank()) {
String encodedReference = Base64.getUrlEncoder().withoutPadding()
.encodeToString(block.getReference().getBytes(StandardCharsets.UTF_8));
return URLSource.builder().url(MEDIA_REFERENCE_SCHEME + encodedReference).build();
}
return URLSource.builder().url(block.getUrl()).build();
}
@@ -286,7 +296,30 @@ public class AgentScopeMessageAdapter {
return;
}
if (source instanceof URLSource urlSource) {
block.setUrl(urlSource.getUrl());
String url = urlSource.getUrl();
if (url != null && url.startsWith(MEDIA_REFERENCE_SCHEME)) {
block.setReference(decodeReference(url));
} else {
block.setUrl(url);
}
}
}
/**
* 从 AgentScope 私有 URLSource 解码业务媒体引用。
*
* @param url 私有协议 URL
* @return 原始业务媒体引用
*/
public static String decodeReference(String url) {
if (url == null || !url.startsWith(MEDIA_REFERENCE_SCHEME)) {
return null;
}
String encoded = url.substring(MEDIA_REFERENCE_SCHEME.length());
try {
return new String(Base64.getUrlDecoder().decode(encoded), StandardCharsets.UTF_8);
} catch (IllegalArgumentException error) {
throw new IllegalArgumentException("Invalid agent media reference.", error);
}
}

View File

@@ -3,6 +3,7 @@ package com.easyagents.agent.runtime.agentscope;
import com.easyagents.agent.runtime.*;
import com.easyagents.agent.runtime.event.*;
import com.easyagents.agent.runtime.event.interceptor.AutoContextInterceptor;
import com.easyagents.agent.runtime.event.interceptor.MediaReferenceInterceptor;
import com.easyagents.agent.runtime.event.interceptor.ToolHitlInterceptor;
import com.easyagents.agent.runtime.event.observer.AgentRuntimeErrorObserver;
import com.easyagents.agent.runtime.event.observer.ReasoningLifecycleObserver;
@@ -303,6 +304,12 @@ public class AgentScopeReActRuntime implements AgentRuntime {
if (userMessage.getContentBlocks() == null || userMessage.getContentBlocks().isEmpty()) {
throw new AgentRuntimeException("Agent user message content is required.");
}
boolean containsImage = userMessage.getContentBlocks().stream()
.anyMatch(block -> block instanceof AgentMediaBlock mediaBlock
&& "image".equalsIgnoreCase(mediaBlock.getMediaKind()));
if (containsImage && !initRequest.getAgentDefinition().getModelSpec().isSupportImage()) {
throw new AgentRuntimeException("The configured model does not support image input.");
}
}
/**
@@ -1087,6 +1094,7 @@ public class AgentScopeReActRuntime implements AgentRuntime {
if (memory instanceof AutoContextMemory) {
interceptors.add(new AutoContextInterceptor(eventBridge, memoryResult.getAutoContextConfig()));
}
interceptors.add(new MediaReferenceInterceptor(initRequest.getMediaResolver()));
List<AgentToolSpec> runtimeToolSpecs = mergeToolSpecs(definition.getToolSpecs(), toolkitBuildResult.mcpToolSpecs(),
toolkitBuildResult.operateToolSpecs());
interceptors.add(new ToolHitlInterceptor(eventBridge, approvalCoordinator,