feat: 打通智能体图片媒体运行链路
- 增加稳定媒体引用解析与模型图片能力校验 - 修复图片 Data URI 与模型完整文本读取 - 补充媒体解析和消息兼容测试
This commit is contained in:
@@ -17,6 +17,7 @@ import com.easyagents.agent.runtime.memory.AgentMemoryPolicy;
|
||||
import com.easyagents.agent.runtime.memory.AgentMemorySnapshot;
|
||||
import com.easyagents.agent.runtime.message.AgentMessage;
|
||||
import com.easyagents.agent.runtime.message.AgentMessageRole;
|
||||
import com.easyagents.agent.runtime.message.AgentMediaBlock;
|
||||
import com.easyagents.agent.runtime.model.AgentModelSpec;
|
||||
import com.easyagents.agent.runtime.persistence.session.memory.InMemoryAgentSessionStore;
|
||||
import com.easyagents.agent.runtime.skill.AgentSkillBoxSpec;
|
||||
@@ -78,6 +79,20 @@ public class AgentScopeStatefulRuntimeTest {
|
||||
runtime.stream(message);
|
||||
}
|
||||
|
||||
@Test(expected = AgentRuntimeException.class)
|
||||
public void shouldRejectImageWhenModelCapabilityIsDisabled() {
|
||||
AgentScopeReActRuntime runtime = fakeRuntime();
|
||||
AgentInitRequest request = initRequest();
|
||||
runtime.init(request);
|
||||
AgentMessage message = new AgentMessage();
|
||||
message.setRole(AgentMessageRole.USER);
|
||||
AgentMediaBlock image = new AgentMediaBlock("image");
|
||||
image.setReference("draft:upload-1");
|
||||
message.setContentBlocks(List.of(image));
|
||||
|
||||
runtime.stream(message);
|
||||
}
|
||||
|
||||
@Test(expected = AgentRuntimeException.class)
|
||||
public void shouldRejectDuplicateInit() {
|
||||
AgentScopeReActRuntime runtime = fakeRuntime();
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
package com.easyagents.agent.runtime.event.interceptor;
|
||||
|
||||
import com.easyagents.agent.runtime.agentscope.AgentScopeMessageAdapter;
|
||||
import com.easyagents.agent.runtime.media.AgentMediaResource;
|
||||
import com.easyagents.agent.runtime.message.AgentMediaBlock;
|
||||
import io.agentscope.core.ReActAgent;
|
||||
import io.agentscope.core.hook.PreReasoningEvent;
|
||||
import io.agentscope.core.message.Base64Source;
|
||||
import io.agentscope.core.message.ImageBlock;
|
||||
import io.agentscope.core.message.Msg;
|
||||
import io.agentscope.core.message.MsgRole;
|
||||
import io.agentscope.core.message.URLSource;
|
||||
import io.agentscope.core.model.ChatResponse;
|
||||
import io.agentscope.core.model.GenerateOptions;
|
||||
import io.agentscope.core.model.Model;
|
||||
import io.agentscope.core.model.ToolSchema;
|
||||
import io.agentscope.core.tool.Toolkit;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
/**
|
||||
* 测试模型调用边界的媒体引用解析。
|
||||
*/
|
||||
public class MediaReferenceInterceptorTest {
|
||||
|
||||
/**
|
||||
* 验证私有引用仅在模型调用边界转换为 Base64,原始记忆消息保持稳定引用。
|
||||
*/
|
||||
@Test
|
||||
public void shouldResolveReferenceToBase64WithoutMutatingOriginalMessage() {
|
||||
AgentScopeMessageAdapter adapter = new AgentScopeMessageAdapter();
|
||||
AgentMediaBlock mediaBlock = new AgentMediaBlock("image");
|
||||
mediaBlock.setReference("draft:upload-1");
|
||||
mediaBlock.setMimeType("image/png");
|
||||
Msg original = Msg.builder()
|
||||
.id("message-1")
|
||||
.name("user")
|
||||
.role(MsgRole.USER)
|
||||
.content(adapter.toContentBlock(mediaBlock))
|
||||
.build();
|
||||
AtomicReference<String> resolvedReference = new AtomicReference<>();
|
||||
MediaReferenceInterceptor interceptor = new MediaReferenceInterceptor(reference -> {
|
||||
resolvedReference.set(reference);
|
||||
return new AgentMediaResource("image/png", "png-data".getBytes(StandardCharsets.UTF_8));
|
||||
});
|
||||
ReActAgent agent = ReActAgent.builder()
|
||||
.name("media-test-agent")
|
||||
.sysPrompt("system")
|
||||
.model(new EmptyModel())
|
||||
.toolkit(new Toolkit())
|
||||
.build();
|
||||
PreReasoningEvent event = new PreReasoningEvent(agent, "reasoning-1", null, List.of(original));
|
||||
|
||||
interceptor.intercept(event).block();
|
||||
|
||||
ImageBlock originalImage = original.getFirstContentBlock(ImageBlock.class);
|
||||
ImageBlock resolvedImage = event.getInputMessages().get(0).getFirstContentBlock(ImageBlock.class);
|
||||
Assert.assertTrue(originalImage.getSource() instanceof URLSource);
|
||||
Assert.assertTrue(((URLSource) originalImage.getSource()).getUrl()
|
||||
.startsWith(AgentScopeMessageAdapter.MEDIA_REFERENCE_SCHEME));
|
||||
Assert.assertTrue(resolvedImage.getSource() instanceof Base64Source);
|
||||
Assert.assertEquals("draft:upload-1", resolvedReference.get());
|
||||
Assert.assertEquals("cG5nLWRhdGE=", ((Base64Source) resolvedImage.getSource()).getData());
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证消息适配器序列化后仍能恢复不透明媒体引用。
|
||||
*/
|
||||
@Test
|
||||
public void shouldRoundTripStableReferenceThroughMessageAdapter() {
|
||||
AgentScopeMessageAdapter adapter = new AgentScopeMessageAdapter();
|
||||
AgentMediaBlock mediaBlock = new AgentMediaBlock("image");
|
||||
mediaBlock.setReference("formal:message-1:0:image/png");
|
||||
|
||||
AgentMediaBlock restored = (AgentMediaBlock) adapter.toAgentBlock(adapter.toContentBlock(mediaBlock));
|
||||
|
||||
Assert.assertEquals("formal:message-1:0:image/png", restored.getReference());
|
||||
Assert.assertNull(restored.getData());
|
||||
Assert.assertNull(restored.getUrl());
|
||||
}
|
||||
|
||||
/**
|
||||
* 仅用于构建真实 AgentScope 事件的空模型。
|
||||
*/
|
||||
private static final class EmptyModel implements Model {
|
||||
|
||||
/**
|
||||
* 返回空响应流,当前测试不会实际调用模型。
|
||||
*
|
||||
* @param messages 输入消息
|
||||
* @param toolSchemas 工具定义
|
||||
* @param options 生成参数
|
||||
* @return 空响应流
|
||||
*/
|
||||
@Override
|
||||
public Flux<ChatResponse> stream(List<Msg> messages,
|
||||
List<ToolSchema> toolSchemas,
|
||||
GenerateOptions options) {
|
||||
return Flux.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回测试模型名称。
|
||||
*
|
||||
* @return 测试模型名称
|
||||
*/
|
||||
@Override
|
||||
public String getModelName() {
|
||||
return "media-test-model";
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user