feat: 先进智能体运行时基座 v1
- 支持高层调用进行可配置智能体业务开发 - 封装基于 agentscope-java - 支持 tool、skill、知识库等封装 - 支持 tool 审批 - 支持智能体事件回传 - 等等
This commit is contained in:
@@ -23,6 +23,18 @@
|
||||
<artifactId>agentscope</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.anthropic</groupId>
|
||||
<artifactId>anthropic-java</artifactId>
|
||||
<version>2.14.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.google.genai</groupId>
|
||||
<artifactId>google-genai</artifactId>
|
||||
<version>1.38.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
|
||||
@@ -0,0 +1,269 @@
|
||||
package com.easyagents.agent.runtime;
|
||||
|
||||
import com.easyagents.agent.runtime.knowledge.AgentKnowledgeSpec;
|
||||
import com.easyagents.agent.runtime.memory.AgentMemoryPolicy;
|
||||
import com.easyagents.agent.runtime.model.AgentGenerationOptions;
|
||||
import com.easyagents.agent.runtime.model.AgentModelSpec;
|
||||
import com.easyagents.agent.runtime.persistence.AgentPersistencePolicy;
|
||||
import com.easyagents.agent.runtime.skill.AgentSkillBoxSpec;
|
||||
import com.easyagents.agent.runtime.tool.AgentToolSpec;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 主入口
|
||||
* ReAct 智能体的声明式定义
|
||||
*/
|
||||
public class AgentDefinition {
|
||||
|
||||
private String agentId;
|
||||
private String agentName;
|
||||
private String description;
|
||||
private String systemPrompt;
|
||||
private AgentModelSpec modelSpec;
|
||||
private AgentGenerationOptions generationOptions = new AgentGenerationOptions();
|
||||
private AgentExecutionOptions executionOptions = new AgentExecutionOptions();
|
||||
private List<AgentToolSpec> toolSpecs = new ArrayList<>();
|
||||
private List<AgentKnowledgeSpec> knowledgeSpecs = new ArrayList<>();
|
||||
private AgentMemoryPolicy memoryPolicy = AgentMemoryPolicy.autoContext();
|
||||
private AgentPersistencePolicy persistencePolicy = AgentPersistencePolicy.disabled();
|
||||
private AgentSkillBoxSpec skillBoxSpec;
|
||||
private Map<String, Object> metadata = new LinkedHashMap<>();
|
||||
|
||||
/**
|
||||
* 获取agent id。
|
||||
*
|
||||
* @return 智能体ID
|
||||
*/
|
||||
public String getAgentId() {
|
||||
return agentId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置agent id。
|
||||
*
|
||||
* @param agentId 智能体ID
|
||||
*/
|
||||
public void setAgentId(String agentId) {
|
||||
this.agentId = agentId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取agent name。
|
||||
*
|
||||
* @return 智能体名称
|
||||
*/
|
||||
public String getAgentName() {
|
||||
return agentName;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置agent name。
|
||||
*
|
||||
* @param agentName 智能体名称
|
||||
*/
|
||||
public void setAgentName(String agentName) {
|
||||
this.agentName = agentName;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取agent description。
|
||||
*
|
||||
* @return 智能体描述
|
||||
*/
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置agent description。
|
||||
*
|
||||
* @param description 智能体描述
|
||||
*/
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取system prompt。
|
||||
*
|
||||
* @return 系统提示词
|
||||
*/
|
||||
public String getSystemPrompt() {
|
||||
return systemPrompt;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置system prompt。
|
||||
*
|
||||
* @param systemPrompt 系统提示词
|
||||
*/
|
||||
public void setSystemPrompt(String systemPrompt) {
|
||||
this.systemPrompt = systemPrompt;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取模型设置。
|
||||
*
|
||||
* @return 模型设置
|
||||
*/
|
||||
public AgentModelSpec getModelSpec() {
|
||||
return modelSpec;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置模型设置。
|
||||
*
|
||||
* @param modelSpec 模型设置
|
||||
*/
|
||||
public void setModelSpec(AgentModelSpec modelSpec) {
|
||||
this.modelSpec = modelSpec;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取生成参数。
|
||||
*
|
||||
* @return 生成参数
|
||||
*/
|
||||
public AgentGenerationOptions getGenerationOptions() {
|
||||
return generationOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置生成参数。
|
||||
*
|
||||
* @param generationOptions 生成参数
|
||||
*/
|
||||
public void setGenerationOptions(AgentGenerationOptions generationOptions) {
|
||||
this.generationOptions = generationOptions == null ? new AgentGenerationOptions() : generationOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取执行参数。
|
||||
*
|
||||
* @return 执行参数
|
||||
*/
|
||||
public AgentExecutionOptions getExecutionOptions() {
|
||||
return executionOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置执行参数。
|
||||
*
|
||||
* @param executionOptions 执行参数
|
||||
*/
|
||||
public void setExecutionOptions(AgentExecutionOptions executionOptions) {
|
||||
this.executionOptions = executionOptions == null ? new AgentExecutionOptions() : executionOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取工具定义。
|
||||
*
|
||||
* @return 工具定义
|
||||
*/
|
||||
public List<AgentToolSpec> getToolSpecs() {
|
||||
return toolSpecs;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置工具定义。
|
||||
*
|
||||
* @param toolSpecs 工具定义
|
||||
*/
|
||||
public void setToolSpecs(List<AgentToolSpec> toolSpecs) {
|
||||
this.toolSpecs = toolSpecs == null ? new ArrayList<>() : new ArrayList<>(toolSpecs);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取知识库定义。
|
||||
*
|
||||
* @return 知识库定义
|
||||
*/
|
||||
public List<AgentKnowledgeSpec> getKnowledgeSpecs() {
|
||||
return knowledgeSpecs;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置知识库定义。
|
||||
*
|
||||
* @param knowledgeSpecs 知识库定义
|
||||
*/
|
||||
public void setKnowledgeSpecs(List<AgentKnowledgeSpec> knowledgeSpecs) {
|
||||
this.knowledgeSpecs = knowledgeSpecs == null ? new ArrayList<>() : new ArrayList<>(knowledgeSpecs);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取记忆策略。
|
||||
*
|
||||
* @return 记忆策略
|
||||
*/
|
||||
public AgentMemoryPolicy getMemoryPolicy() {
|
||||
return memoryPolicy;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置记忆策略。
|
||||
*
|
||||
* @param memoryPolicy 记忆策略
|
||||
*/
|
||||
public void setMemoryPolicy(AgentMemoryPolicy memoryPolicy) {
|
||||
this.memoryPolicy = memoryPolicy == null ? AgentMemoryPolicy.autoContext() : memoryPolicy;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取持久化策略。
|
||||
*
|
||||
* @return 持久化策略
|
||||
*/
|
||||
public AgentPersistencePolicy getPersistencePolicy() {
|
||||
return persistencePolicy;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置持久化策略。
|
||||
*
|
||||
* @param persistencePolicy 持久化策略
|
||||
*/
|
||||
public void setPersistencePolicy(AgentPersistencePolicy persistencePolicy) {
|
||||
this.persistencePolicy = persistencePolicy == null ? AgentPersistencePolicy.disabled() : persistencePolicy;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取SkillBox 设置。
|
||||
*
|
||||
* @return SkillBox 设置
|
||||
*/
|
||||
public AgentSkillBoxSpec getSkillBoxSpec() {
|
||||
return skillBoxSpec;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置SkillBox 设置。
|
||||
*
|
||||
* @param skillBoxSpec SkillBox 设置
|
||||
*/
|
||||
public void setSkillBoxSpec(AgentSkillBoxSpec skillBoxSpec) {
|
||||
this.skillBoxSpec = skillBoxSpec;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取元数据。
|
||||
*
|
||||
* @return 元数据
|
||||
*/
|
||||
public Map<String, Object> getMetadata() {
|
||||
return metadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置元数据。
|
||||
*
|
||||
* @param metadata 元数据
|
||||
*/
|
||||
public void setMetadata(Map<String, Object> metadata) {
|
||||
this.metadata = metadata == null ? new LinkedHashMap<>() : metadata;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
package com.easyagents.agent.runtime;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
/**
|
||||
* 运行时执行参数。
|
||||
*/
|
||||
public class AgentExecutionOptions {
|
||||
|
||||
private int maxIters = 10;
|
||||
private Duration timeout = Duration.ofMinutes(5);
|
||||
private boolean reasoningEnabled = true;
|
||||
private boolean toolCallingEnabled = true;
|
||||
|
||||
/**
|
||||
* 获取最大 ReAct 迭代次数。
|
||||
*
|
||||
* @return 最大迭代次数
|
||||
*/
|
||||
public int getMaxIters() {
|
||||
return maxIters;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置最大 ReAct 迭代次数。
|
||||
*
|
||||
* @param maxIters 最大迭代次数
|
||||
*/
|
||||
public void setMaxIters(int maxIters) {
|
||||
this.maxIters = maxIters <= 0 ? 10 : maxIters;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取超时时间。
|
||||
*
|
||||
* @return 超时时间
|
||||
*/
|
||||
public Duration getTimeout() {
|
||||
return timeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置超时时间。
|
||||
*
|
||||
* @param timeout 超时时间
|
||||
*/
|
||||
public void setTimeout(Duration timeout) {
|
||||
this.timeout = timeout == null ? Duration.ofMinutes(5) : timeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回是否reasoning events are enabled。
|
||||
*
|
||||
* @return 启用时为 true
|
||||
*/
|
||||
public boolean isReasoningEnabled() {
|
||||
return reasoningEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置whether reasoning events are enabled。
|
||||
*
|
||||
* @param reasoningEnabled 启用时为 true
|
||||
*/
|
||||
public void setReasoningEnabled(boolean reasoningEnabled) {
|
||||
this.reasoningEnabled = reasoningEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回是否tool calls are enabled。
|
||||
*
|
||||
* @return 启用时为 true
|
||||
*/
|
||||
public boolean isToolCallingEnabled() {
|
||||
return toolCallingEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置whether tool calls are enabled。
|
||||
*
|
||||
* @param toolCallingEnabled 启用时为 true
|
||||
*/
|
||||
public void setToolCallingEnabled(boolean toolCallingEnabled) {
|
||||
this.toolCallingEnabled = toolCallingEnabled;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.easyagents.agent.runtime;
|
||||
|
||||
import com.easyagents.agent.runtime.event.AgentRuntimeEvent;
|
||||
import com.easyagents.agent.runtime.hitl.AgentToolApprovalResponse;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
/**
|
||||
* 可取消的单次智能体运行句柄。
|
||||
*/
|
||||
public interface AgentRunHandle {
|
||||
|
||||
/**
|
||||
* 获取本次运行的事件流。
|
||||
*
|
||||
* @return 事件流
|
||||
*/
|
||||
Flux<AgentRuntimeEvent> stream();
|
||||
|
||||
/**
|
||||
* 取消本次运行。
|
||||
*/
|
||||
void cancel();
|
||||
|
||||
/**
|
||||
* 提交工具审批结果。
|
||||
*
|
||||
* @param response 审批响应
|
||||
*/
|
||||
void submitToolApproval(AgentToolApprovalResponse response);
|
||||
}
|
||||
@@ -0,0 +1,267 @@
|
||||
package com.easyagents.agent.runtime;
|
||||
|
||||
import com.easyagents.agent.runtime.knowledge.AgentKnowledgeRetriever;
|
||||
import com.easyagents.agent.runtime.memory.AgentMemorySnapshot;
|
||||
import com.easyagents.agent.runtime.message.AgentMessage;
|
||||
import com.easyagents.agent.runtime.persistence.AgentConversationRecorder;
|
||||
import com.easyagents.agent.runtime.persistence.AgentSessionStore;
|
||||
import com.easyagents.agent.runtime.persistence.noop.NoopAgentConversationRecorder;
|
||||
import com.easyagents.agent.runtime.persistence.noop.NoopAgentSessionStore;
|
||||
import com.easyagents.agent.runtime.tool.AgentToolInvoker;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 一次智能体运行请求。
|
||||
*/
|
||||
public class AgentRunRequest {
|
||||
|
||||
private String requestId;
|
||||
private String traceId;
|
||||
private String sessionId;
|
||||
private AgentDefinition agentDefinition;
|
||||
private AgentRuntimeContext runtimeContext = new AgentRuntimeContext();
|
||||
private AgentMessage userMessage;
|
||||
private AgentMemorySnapshot memorySnapshot = new AgentMemorySnapshot();
|
||||
private Map<String, AgentToolInvoker> toolInvokers = new LinkedHashMap<>();
|
||||
private Map<String, AgentKnowledgeRetriever> knowledgeRetrievers = new LinkedHashMap<>();
|
||||
private AgentSessionStore sessionStore = NoopAgentSessionStore.INSTANCE;
|
||||
private AgentConversationRecorder conversationRecorder = NoopAgentConversationRecorder.INSTANCE;
|
||||
private Map<String, Object> metadata = new LinkedHashMap<>();
|
||||
private String cancelReason;
|
||||
|
||||
/**
|
||||
* 获取请求ID。
|
||||
*
|
||||
* @return 请求ID
|
||||
*/
|
||||
public String getRequestId() {
|
||||
return requestId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置请求ID。
|
||||
*
|
||||
* @param requestId 请求ID
|
||||
*/
|
||||
public void setRequestId(String requestId) {
|
||||
this.requestId = requestId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取链路ID。
|
||||
*
|
||||
* @return 链路ID
|
||||
*/
|
||||
public String getTraceId() {
|
||||
return traceId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置链路ID。
|
||||
*
|
||||
* @param traceId 链路ID
|
||||
*/
|
||||
public void setTraceId(String traceId) {
|
||||
this.traceId = traceId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取会话ID。
|
||||
*
|
||||
* @return 会话ID
|
||||
*/
|
||||
public String getSessionId() {
|
||||
return sessionId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置会话ID。
|
||||
*
|
||||
* @param sessionId 会话ID
|
||||
*/
|
||||
public void setSessionId(String sessionId) {
|
||||
this.sessionId = sessionId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取智能体定义。
|
||||
*
|
||||
* @return 智能体定义
|
||||
*/
|
||||
public AgentDefinition getAgentDefinition() {
|
||||
return agentDefinition;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置智能体定义。
|
||||
*
|
||||
* @param agentDefinition 智能体定义
|
||||
*/
|
||||
public void setAgentDefinition(AgentDefinition agentDefinition) {
|
||||
this.agentDefinition = agentDefinition;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取运行时上下文。
|
||||
*
|
||||
* @return 运行时上下文
|
||||
*/
|
||||
public AgentRuntimeContext getRuntimeContext() {
|
||||
return runtimeContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置运行时上下文。
|
||||
*
|
||||
* @param runtimeContext 运行时上下文
|
||||
*/
|
||||
public void setRuntimeContext(AgentRuntimeContext runtimeContext) {
|
||||
this.runtimeContext = runtimeContext == null ? new AgentRuntimeContext() : runtimeContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户消息。
|
||||
*
|
||||
* @return 用户消息
|
||||
*/
|
||||
public AgentMessage getUserMessage() {
|
||||
return userMessage;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置用户消息。
|
||||
*
|
||||
* @param userMessage 用户消息
|
||||
*/
|
||||
public void setUserMessage(AgentMessage userMessage) {
|
||||
this.userMessage = userMessage;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取记忆快照。
|
||||
*
|
||||
* @return 记忆快照
|
||||
*/
|
||||
public AgentMemorySnapshot getMemorySnapshot() {
|
||||
return memorySnapshot;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置记忆快照。
|
||||
*
|
||||
* @param memorySnapshot 记忆快照
|
||||
*/
|
||||
public void setMemorySnapshot(AgentMemorySnapshot memorySnapshot) {
|
||||
this.memorySnapshot = memorySnapshot == null ? new AgentMemorySnapshot() : memorySnapshot;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取tool invokers by tool name。
|
||||
*
|
||||
* @return 工具调用器
|
||||
*/
|
||||
public Map<String, AgentToolInvoker> getToolInvokers() {
|
||||
return toolInvokers;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置工具调用器。
|
||||
*
|
||||
* @param toolInvokers 工具调用器
|
||||
*/
|
||||
public void setToolInvokers(Map<String, AgentToolInvoker> toolInvokers) {
|
||||
this.toolInvokers = toolInvokers == null ? new LinkedHashMap<>() : toolInvokers;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取knowledge retrievers by knowledge id。
|
||||
*
|
||||
* @return 知识库检索器
|
||||
*/
|
||||
public Map<String, AgentKnowledgeRetriever> getKnowledgeRetrievers() {
|
||||
return knowledgeRetrievers;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置知识库检索器。
|
||||
*
|
||||
* @param knowledgeRetrievers 知识库检索器
|
||||
*/
|
||||
public void setKnowledgeRetrievers(Map<String, AgentKnowledgeRetriever> knowledgeRetrievers) {
|
||||
this.knowledgeRetrievers = knowledgeRetrievers == null ? new LinkedHashMap<>() : knowledgeRetrievers;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取会话存储。
|
||||
*
|
||||
* @return 会话存储
|
||||
*/
|
||||
public AgentSessionStore getSessionStore() {
|
||||
return sessionStore;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置会话存储。
|
||||
*
|
||||
* @param sessionStore 会话存储
|
||||
*/
|
||||
public void setSessionStore(AgentSessionStore sessionStore) {
|
||||
this.sessionStore = sessionStore == null ? NoopAgentSessionStore.INSTANCE : sessionStore;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取会话记录器。
|
||||
*
|
||||
* @return 会话记录器
|
||||
*/
|
||||
public AgentConversationRecorder getConversationRecorder() {
|
||||
return conversationRecorder;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置会话记录器。
|
||||
*
|
||||
* @param conversationRecorder 会话记录器
|
||||
*/
|
||||
public void setConversationRecorder(AgentConversationRecorder conversationRecorder) {
|
||||
this.conversationRecorder = conversationRecorder == null ? NoopAgentConversationRecorder.INSTANCE : conversationRecorder;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取元数据。
|
||||
*
|
||||
* @return 元数据
|
||||
*/
|
||||
public Map<String, Object> getMetadata() {
|
||||
return metadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置元数据。
|
||||
*
|
||||
* @param metadata 元数据
|
||||
*/
|
||||
public void setMetadata(Map<String, Object> metadata) {
|
||||
this.metadata = metadata == null ? new LinkedHashMap<>() : metadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取取消原因。
|
||||
*
|
||||
* @return 取消原因
|
||||
*/
|
||||
public String getCancelReason() {
|
||||
return cancelReason;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置取消原因。
|
||||
*
|
||||
* @param cancelReason 取消原因
|
||||
*/
|
||||
public void setCancelReason(String cancelReason) {
|
||||
this.cancelReason = cancelReason;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package com.easyagents.agent.runtime;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 智能体运行的最终结果。
|
||||
*/
|
||||
public class AgentRunResult {
|
||||
|
||||
private String content;
|
||||
private Map<String, Object> metadata = new LinkedHashMap<>();
|
||||
|
||||
/**
|
||||
* 获取最终内容。
|
||||
*
|
||||
* @return 最终内容
|
||||
*/
|
||||
public String getContent() {
|
||||
return content;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置最终内容。
|
||||
*
|
||||
* @param content 最终内容
|
||||
*/
|
||||
public void setContent(String content) {
|
||||
this.content = content;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取元数据。
|
||||
*
|
||||
* @return 元数据
|
||||
*/
|
||||
public Map<String, Object> getMetadata() {
|
||||
return metadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置元数据。
|
||||
*
|
||||
* @param metadata 元数据
|
||||
*/
|
||||
public void setMetadata(Map<String, Object> metadata) {
|
||||
this.metadata = metadata == null ? new LinkedHashMap<>() : metadata;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.easyagents.agent.runtime;
|
||||
|
||||
import com.easyagents.agent.runtime.event.AgentRuntimeEvent;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
/**
|
||||
* 执行声明式 ReAct 智能体并流式输出运行事件。
|
||||
*/
|
||||
public interface AgentRuntime {
|
||||
|
||||
/**
|
||||
* 启动单次智能体运行并返回可取消句柄。
|
||||
*
|
||||
* @param request 包含定义、输入、记忆和适配器的运行请求
|
||||
* @return 本次运行句柄
|
||||
*/
|
||||
AgentRunHandle start(AgentRunRequest request);
|
||||
|
||||
/**
|
||||
* 启动单次智能体运行。
|
||||
*
|
||||
* @param request 包含定义、输入、记忆和适配器的运行请求
|
||||
* @return 本次运行的事件流
|
||||
*/
|
||||
Flux<AgentRuntimeEvent> stream(AgentRunRequest request);
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
package com.easyagents.agent.runtime;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 传递给工具和检索器的中立运行时上下文。
|
||||
*/
|
||||
public class AgentRuntimeContext {
|
||||
|
||||
private String tenantId;
|
||||
private String userId;
|
||||
private String userName;
|
||||
private String sessionId;
|
||||
private String traceId;
|
||||
private Map<String, Object> metadata = new LinkedHashMap<>();
|
||||
|
||||
/**
|
||||
* 获取租户ID。
|
||||
*
|
||||
* @return 租户ID
|
||||
*/
|
||||
public String getTenantId() {
|
||||
return tenantId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置租户ID。
|
||||
*
|
||||
* @param tenantId 租户ID
|
||||
*/
|
||||
public void setTenantId(String tenantId) {
|
||||
this.tenantId = tenantId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户ID。
|
||||
*
|
||||
* @return 用户ID
|
||||
*/
|
||||
public String getUserId() {
|
||||
return userId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置用户ID。
|
||||
*
|
||||
* @param userId 用户ID
|
||||
*/
|
||||
public void setUserId(String userId) {
|
||||
this.userId = userId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户名。
|
||||
*
|
||||
* @return 用户名
|
||||
*/
|
||||
public String getUserName() {
|
||||
return userName;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置用户名。
|
||||
*
|
||||
* @param userName 用户名
|
||||
*/
|
||||
public void setUserName(String userName) {
|
||||
this.userName = userName;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取会话ID。
|
||||
*
|
||||
* @return 会话ID
|
||||
*/
|
||||
public String getSessionId() {
|
||||
return sessionId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置会话ID。
|
||||
*
|
||||
* @param sessionId 会话ID
|
||||
*/
|
||||
public void setSessionId(String sessionId) {
|
||||
this.sessionId = sessionId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取链路ID。
|
||||
*
|
||||
* @return 链路ID
|
||||
*/
|
||||
public String getTraceId() {
|
||||
return traceId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置链路ID。
|
||||
*
|
||||
* @param traceId 链路ID
|
||||
*/
|
||||
public void setTraceId(String traceId) {
|
||||
this.traceId = traceId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取元数据。
|
||||
*
|
||||
* @return 元数据
|
||||
*/
|
||||
public Map<String, Object> getMetadata() {
|
||||
return metadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置元数据。
|
||||
*
|
||||
* @param metadata 元数据
|
||||
*/
|
||||
public void setMetadata(Map<String, Object> metadata) {
|
||||
this.metadata = metadata == null ? new LinkedHashMap<>() : metadata;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.easyagents.agent.runtime;
|
||||
|
||||
/**
|
||||
* 智能体运行时基础设施抛出的异常。
|
||||
*/
|
||||
public class AgentRuntimeException extends RuntimeException {
|
||||
|
||||
/**
|
||||
* 创建包含错误消息的异常。
|
||||
*
|
||||
* @param message 错误消息
|
||||
*/
|
||||
public AgentRuntimeException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建包含错误消息和根因的异常。
|
||||
*
|
||||
* @param message 错误消息
|
||||
* @param cause 根因
|
||||
*/
|
||||
public AgentRuntimeException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.easyagents.agent.runtime.agentscope;
|
||||
|
||||
import com.easyagents.agent.runtime.AgentRunRequest;
|
||||
import io.agentscope.core.hook.Hook;
|
||||
import io.agentscope.core.hook.HookEvent;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
/**
|
||||
* 从 AgentScope Hook 回调发射不在主事件流中的运行时事件。
|
||||
*/
|
||||
public class AgentScopeEventHook implements Hook {
|
||||
|
||||
private final AgentRunRequest request;
|
||||
|
||||
/**
|
||||
* 创建 Hook。
|
||||
*
|
||||
* @param request 运行请求
|
||||
*/
|
||||
public AgentScopeEventHook(AgentRunRequest request) {
|
||||
this.request = request;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends HookEvent> Mono<T> onEvent(T event) {
|
||||
return Mono.just(event);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,240 @@
|
||||
package com.easyagents.agent.runtime.agentscope;
|
||||
|
||||
import com.easyagents.agent.runtime.AgentRunRequest;
|
||||
import com.easyagents.agent.runtime.AgentRuntimeException;
|
||||
import com.easyagents.agent.runtime.event.AgentRuntimeEvent;
|
||||
import com.easyagents.agent.runtime.event.AgentRuntimeEventType;
|
||||
import com.easyagents.agent.runtime.knowledge.*;
|
||||
import io.agentscope.core.message.TextBlock;
|
||||
import io.agentscope.core.rag.Knowledge;
|
||||
import io.agentscope.core.rag.model.Document;
|
||||
import io.agentscope.core.rag.model.DocumentMetadata;
|
||||
import io.agentscope.core.rag.model.RetrieveConfig;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.core.publisher.Sinks;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* 将运行时知识库检索器适配为一个聚合 AgentScope Knowledge。
|
||||
*/
|
||||
public class AgentScopeKnowledgeAdapter {
|
||||
|
||||
/**
|
||||
* 创建聚合 Knowledge。
|
||||
*
|
||||
* @param request 运行请求
|
||||
* @return 聚合 Knowledge;未配置知识库时返回 null
|
||||
*/
|
||||
public Knowledge createAggregateKnowledge(AgentRunRequest request) {
|
||||
return createAggregateKnowledge(request, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建带事件 sink 的聚合 Knowledge。
|
||||
*
|
||||
* @param request 运行请求
|
||||
* @param eventSink 事件 sink
|
||||
* @return 聚合 Knowledge;未配置知识库时返回 null
|
||||
*/
|
||||
public Knowledge createAggregateKnowledge(AgentRunRequest request, Sinks.Many<AgentRuntimeEvent> eventSink) {
|
||||
if (request.getAgentDefinition().getKnowledgeSpecs().isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return new AggregateKnowledge(request, eventSink);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将运行时文档转换为 AgentScope 文档。
|
||||
*
|
||||
* @param documents 运行时文档
|
||||
* @return AgentScope 文档
|
||||
*/
|
||||
public List<Document> toDocuments(List<AgentKnowledgeDocument> documents) {
|
||||
List<Document> converted = new ArrayList<>();
|
||||
if (documents == null) {
|
||||
return converted;
|
||||
}
|
||||
for (AgentKnowledgeDocument document : documents) {
|
||||
converted.add(toDocument(document));
|
||||
}
|
||||
return converted;
|
||||
}
|
||||
|
||||
private Document toDocument(AgentKnowledgeDocument document) {
|
||||
Map<String, Object> payload = new LinkedHashMap<>();
|
||||
payload.put("documentId", document.getDocumentId());
|
||||
payload.put("documentName", document.getDocumentName());
|
||||
payload.put("chunkId", document.getChunkId());
|
||||
payload.put("sourceUri", document.getSourceUri());
|
||||
payload.put("knowledgeMetadata", document.getKnowledgeMetadata());
|
||||
payload.put("documentMetadata", document.getMetadata());
|
||||
payload.putAll(document.getMetadata());
|
||||
DocumentMetadata metadata = DocumentMetadata.builder()
|
||||
.content(TextBlock.builder().text(document.getContent()).build())
|
||||
.docId(document.getDocumentId())
|
||||
.chunkId(document.getChunkId())
|
||||
.payload(payload)
|
||||
.build();
|
||||
Document converted = new Document(metadata);
|
||||
converted.setScore(document.getScore());
|
||||
return converted;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将检索调用分发到多个知识源的聚合 Knowledge 实现。
|
||||
*/
|
||||
private class AggregateKnowledge implements Knowledge {
|
||||
|
||||
private final AgentRunRequest request;
|
||||
private final Sinks.Many<AgentRuntimeEvent> eventSink;
|
||||
|
||||
private AggregateKnowledge(AgentRunRequest request, Sinks.Many<AgentRuntimeEvent> eventSink) {
|
||||
this.request = request;
|
||||
this.eventSink = eventSink;
|
||||
}
|
||||
|
||||
/**
|
||||
* 忽略文档新增,因为知识库索引由 EasyFlow 负责。
|
||||
*
|
||||
* @param documents 文档列表
|
||||
* @return 完成信号
|
||||
*/
|
||||
@Override
|
||||
public Mono<Void> addDocuments(List<Document> documents) {
|
||||
return Mono.error(new UnsupportedOperationException(
|
||||
"Easy-Agents agent runtime knowledge does not support addDocuments. Use external knowledge service instead."));
|
||||
}
|
||||
|
||||
/**
|
||||
* 从已配置的知识源检索文档。
|
||||
*
|
||||
* @param query 查询
|
||||
* @param config 检索配置
|
||||
* @return 文档列表
|
||||
*/
|
||||
@Override
|
||||
public Mono<List<Document>> retrieve(String query, RetrieveConfig config) {
|
||||
return Mono.fromCallable(() -> retrieveAll(query, config));
|
||||
}
|
||||
|
||||
/**
|
||||
* 检索并合并所有已配置的知识源。
|
||||
*
|
||||
* @param query 查询
|
||||
* @param config 检索配置
|
||||
* @return 合并后的文档
|
||||
*/
|
||||
private List<Document> retrieveAll(String query, RetrieveConfig config) {
|
||||
List<AgentKnowledgeDocument> allDocuments = new ArrayList<>();
|
||||
int globalLimit = config == null || config.getLimit() <= 0 ? 5 : config.getLimit();
|
||||
double globalThreshold = config == null ? 0D : config.getScoreThreshold();
|
||||
for (AgentKnowledgeSpec spec : request.getAgentDefinition().getKnowledgeSpecs()) {
|
||||
AgentKnowledgeRetriever retriever = request.getKnowledgeRetrievers().get(spec.getKnowledgeId());
|
||||
if (retriever == null) {
|
||||
throw new AgentRuntimeException("Knowledge retriever is required: " + spec.getKnowledgeId());
|
||||
}
|
||||
AgentKnowledgeRetrievalRequest retrievalRequest = new AgentKnowledgeRetrievalRequest();
|
||||
retrievalRequest.setQuery(query);
|
||||
retrievalRequest.setLimit(spec.getLimit());
|
||||
retrievalRequest.setScoreThreshold(Math.max(spec.getScoreThreshold(), globalThreshold));
|
||||
retrievalRequest.setKnowledgeSpec(spec);
|
||||
retrievalRequest.setRuntimeContext(request.getRuntimeContext());
|
||||
retrievalRequest.getMetadata().put("traceId", request.getTraceId());
|
||||
retrievalRequest.getMetadata().put("sessionId", request.getSessionId());
|
||||
AgentKnowledgeRetrievalResult result = retriever.retrieve(retrievalRequest);
|
||||
if (result == null || result.getDocuments() == null) {
|
||||
emitKnowledgeRetrievalEvent(query, spec, retrievalRequest, new ArrayList<>());
|
||||
continue;
|
||||
}
|
||||
emitKnowledgeRetrievalEvent(query, spec, retrievalRequest, result.getDocuments());
|
||||
for (AgentKnowledgeDocument document : result.getDocuments()) {
|
||||
preserveKnowledgeMetadata(spec, document);
|
||||
allDocuments.add(document);
|
||||
}
|
||||
}
|
||||
allDocuments.sort(Comparator.comparing(
|
||||
AgentKnowledgeDocument::getScore,
|
||||
Comparator.nullsLast(Comparator.reverseOrder())
|
||||
));
|
||||
if (allDocuments.size() > globalLimit) {
|
||||
allDocuments = new ArrayList<>(allDocuments.subList(0, globalLimit));
|
||||
}
|
||||
return toDocuments(allDocuments);
|
||||
}
|
||||
|
||||
/**
|
||||
* 在单条文档上保留知识库级元数据。
|
||||
*
|
||||
* @param spec 知识库声明
|
||||
* @param document 文档
|
||||
*/
|
||||
private void preserveKnowledgeMetadata(AgentKnowledgeSpec spec, AgentKnowledgeDocument document) {
|
||||
Map<String, Object> knowledgeMetadata = new LinkedHashMap<>(spec.getMetadata());
|
||||
knowledgeMetadata.put("knowledgeId", spec.getKnowledgeId());
|
||||
knowledgeMetadata.put("knowledgeName", spec.getName());
|
||||
knowledgeMetadata.put("retrievalMode", spec.getRetrievalMode().name());
|
||||
knowledgeMetadata.putAll(document.getKnowledgeMetadata());
|
||||
document.setKnowledgeMetadata(knowledgeMetadata);
|
||||
document.getMetadata().putIfAbsent("knowledgeId", spec.getKnowledgeId());
|
||||
document.getMetadata().putIfAbsent("knowledgeName", spec.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* 发射知识库检索事件,供聊天界面展示检索过程。
|
||||
*
|
||||
* @param query 查询
|
||||
* @param spec 知识库声明
|
||||
* @param retrievalRequest 检索请求
|
||||
* @param documents 检索文档
|
||||
*/
|
||||
private void emitKnowledgeRetrievalEvent(String query,
|
||||
AgentKnowledgeSpec spec,
|
||||
AgentKnowledgeRetrievalRequest retrievalRequest,
|
||||
List<AgentKnowledgeDocument> documents) {
|
||||
if (eventSink == null) {
|
||||
return;
|
||||
}
|
||||
AgentRuntimeEvent event = AgentRuntimeEvent.of(AgentRuntimeEventType.KNOWLEDGE_RETRIEVAL);
|
||||
event.setTraceId(request.getTraceId());
|
||||
event.setSessionId(request.getSessionId());
|
||||
event.setAgentId(request.getAgentDefinition().getAgentId());
|
||||
event.getMetadata().put("requestId", request.getRequestId());
|
||||
event.getPayload().put("query", query);
|
||||
event.getPayload().put("knowledgeId", spec.getKnowledgeId());
|
||||
event.getPayload().put("knowledgeName", spec.getName());
|
||||
event.getPayload().put("limit", retrievalRequest.getLimit());
|
||||
event.getPayload().put("scoreThreshold", retrievalRequest.getScoreThreshold());
|
||||
event.getPayload().put("documentCount", documents == null ? 0 : documents.size());
|
||||
event.getPayload().put("documents", documentSummaries(documents));
|
||||
Sinks.EmitResult result = eventSink.tryEmitNext(event);
|
||||
if (result.isFailure()) {
|
||||
throw new AgentRuntimeException("Failed to emit knowledge retrieval event: " + result);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建用于事件展示的文档摘要,避免把全文内容放入事件。
|
||||
*
|
||||
* @param documents 检索文档
|
||||
* @return 文档摘要列表
|
||||
*/
|
||||
private List<Map<String, Object>> documentSummaries(List<AgentKnowledgeDocument> documents) {
|
||||
List<Map<String, Object>> summaries = new ArrayList<>();
|
||||
if (documents == null) {
|
||||
return summaries;
|
||||
}
|
||||
for (AgentKnowledgeDocument document : documents) {
|
||||
Map<String, Object> summary = new LinkedHashMap<>();
|
||||
summary.put("documentId", document.getDocumentId());
|
||||
summary.put("documentName", document.getDocumentName());
|
||||
summary.put("chunkId", document.getChunkId());
|
||||
summary.put("score", document.getScore());
|
||||
summary.put("sourceUri", document.getSourceUri());
|
||||
summary.put("metadata", document.getMetadata());
|
||||
summaries.add(summary);
|
||||
}
|
||||
return summaries;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package com.easyagents.agent.runtime.agentscope;
|
||||
|
||||
import com.easyagents.agent.runtime.memory.AgentMemoryCompressionParameter;
|
||||
import com.easyagents.agent.runtime.memory.AgentMemoryPolicy;
|
||||
import com.easyagents.agent.runtime.memory.AgentMemorySnapshot;
|
||||
import com.easyagents.agent.runtime.message.AgentMessage;
|
||||
import io.agentscope.core.memory.InMemoryMemory;
|
||||
import io.agentscope.core.memory.Memory;
|
||||
import io.agentscope.core.memory.autocontext.AutoContextConfig;
|
||||
import io.agentscope.core.memory.autocontext.AutoContextMemory;
|
||||
import io.agentscope.core.model.Model;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 将运行时记忆设置适配为 AgentScope 记忆。
|
||||
*/
|
||||
public class AgentScopeMemoryAdapter {
|
||||
|
||||
private final AgentScopeMessageAdapter messageAdapter = new AgentScopeMessageAdapter();
|
||||
|
||||
/**
|
||||
* 创建记忆并附加恢复后的历史消息。
|
||||
*
|
||||
* @param snapshot 记忆快照
|
||||
* @param policy 记忆策略
|
||||
* @param model 用于自动压缩的模型
|
||||
* @return AgentScope 记忆
|
||||
*/
|
||||
public Memory createMemory(AgentMemorySnapshot snapshot, AgentMemoryPolicy policy, Model model) {
|
||||
AgentMemoryPolicy safePolicy = policy == null ? AgentMemoryPolicy.autoContext() : policy;
|
||||
Memory memory;
|
||||
if (safePolicy.getType() == com.easyagents.agent.runtime.memory.AgentMemoryType.AUTO_CONTEXT
|
||||
&& safePolicy.getCompressionParameter().isEnabled()) {
|
||||
memory = new AutoContextMemory(toAutoContextConfig(safePolicy.getCompressionParameter()), model);
|
||||
} else {
|
||||
memory = new InMemoryMemory();
|
||||
}
|
||||
attachMessages(memory, snapshot, safePolicy.getMaxAttachedMessageCount());
|
||||
return memory;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将自动压缩参数转换为 AgentScope 配置。
|
||||
*
|
||||
* @param parameter 自动压缩参数
|
||||
* @return AgentScope 配置
|
||||
*/
|
||||
public AutoContextConfig toAutoContextConfig(AgentMemoryCompressionParameter parameter) {
|
||||
AgentMemoryCompressionParameter safeParameter = parameter == null ? new AgentMemoryCompressionParameter() : parameter;
|
||||
return AutoContextConfig.builder()
|
||||
.msgThreshold(safeParameter.getMsgThreshold())
|
||||
.lastKeep(safeParameter.getLastKeep())
|
||||
.tokenRatio(safeParameter.getTokenRatio())
|
||||
.maxToken(safeParameter.getMaxToken())
|
||||
.largePayloadThreshold(safeParameter.getLargePayloadThreshold())
|
||||
.minCompressionTokenThreshold(safeParameter.getMinCompressionTokenThreshold())
|
||||
.currentRoundCompressionRatio(safeParameter.getCurrentRoundCompressionRatio())
|
||||
.minConsecutiveToolMessages(safeParameter.getMinConsecutiveToolMessages())
|
||||
.build();
|
||||
}
|
||||
|
||||
private void attachMessages(Memory memory, AgentMemorySnapshot snapshot, int maxAttachedMessageCount) {
|
||||
if (snapshot == null || snapshot.getMessages().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
List<AgentMessage> messages = snapshot.getMessages();
|
||||
int fromIndex = Math.max(0, messages.size() - maxAttachedMessageCount);
|
||||
for (AgentMessage message : messages.subList(fromIndex, messages.size())) {
|
||||
memory.addMessage(messageAdapter.toMsg(message));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,345 @@
|
||||
package com.easyagents.agent.runtime.agentscope;
|
||||
|
||||
import com.easyagents.agent.runtime.message.*;
|
||||
import io.agentscope.core.message.*;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 运行时消息与 AgentScope 消息的双向适配器。
|
||||
*/
|
||||
public class AgentScopeMessageAdapter {
|
||||
|
||||
/**
|
||||
* 将运行时消息转换为 AgentScope 消息。
|
||||
*
|
||||
* @param message 运行时消息
|
||||
* @return AgentScope 消息
|
||||
*/
|
||||
public Msg toMsg(AgentMessage message) {
|
||||
if (message == null) {
|
||||
return null;
|
||||
}
|
||||
return Msg.builder()
|
||||
.id(message.getMessageId())
|
||||
.name(message.getName())
|
||||
.role(toRole(message.getRole()))
|
||||
.content(toContentBlocks(message.getContentBlocks()))
|
||||
.metadata(copyMap(message.getMetadata()))
|
||||
.timestamp(message.getCreatedAt() == null ? null : message.getCreatedAt().toString())
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 AgentScope 消息转换为运行时消息。
|
||||
*
|
||||
* @param msg AgentScope 消息
|
||||
* @return 运行时消息
|
||||
*/
|
||||
public AgentMessage toAgentMessage(Msg msg) {
|
||||
if (msg == null) {
|
||||
return null;
|
||||
}
|
||||
AgentMessage message = new AgentMessage();
|
||||
message.setMessageId(msg.getId());
|
||||
message.setName(msg.getName());
|
||||
message.setRole(toRole(msg.getRole()));
|
||||
message.setMetadata(msg.getMetadata());
|
||||
if (msg.getTimestamp() != null && !msg.getTimestamp().isBlank()) {
|
||||
Instant parsed = parseTimestamp(msg.getTimestamp());
|
||||
if (parsed != null) {
|
||||
message.setCreatedAt(parsed);
|
||||
} else {
|
||||
message.getMetadata().put("rawTimestamp", msg.getTimestamp());
|
||||
}
|
||||
}
|
||||
message.setContentBlocks(toBlocks(msg.getContent()));
|
||||
return message;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 AgentScope 块转换为运行时块。
|
||||
*
|
||||
* @param block AgentScope 块
|
||||
* @return 运行时块
|
||||
*/
|
||||
public AgentContentBlock toAgentBlock(ContentBlock block) {
|
||||
if (block == null) {
|
||||
return null;
|
||||
}
|
||||
if (block instanceof TextBlock textBlock) {
|
||||
return new AgentTextBlock(textBlock.getText());
|
||||
}
|
||||
if (block instanceof ThinkingBlock thinkingBlock) {
|
||||
AgentThinkingBlock converted = new AgentThinkingBlock(thinkingBlock.getThinking());
|
||||
converted.getMetadata().putAll(copyMap(thinkingBlock.getMetadata()));
|
||||
return converted;
|
||||
}
|
||||
if (block instanceof ToolUseBlock toolUseBlock) {
|
||||
AgentToolUseBlock converted = new AgentToolUseBlock(toolUseBlock.getId(), toolUseBlock.getName(), copyMap(toolUseBlock.getInput()));
|
||||
converted.setContent(toolUseBlock.getContent());
|
||||
converted.getMetadata().putAll(copyMap(toolUseBlock.getMetadata()));
|
||||
return converted;
|
||||
}
|
||||
if (block instanceof ToolResultBlock toolResultBlock) {
|
||||
AgentToolResultBlock converted = new AgentToolResultBlock(toolResultBlock.getId(), toolResultBlock.getName());
|
||||
converted.setOutput(toBlocks(toolResultBlock.getOutput()));
|
||||
converted.setSuspended(toolResultBlock.isSuspended());
|
||||
converted.getMetadata().putAll(copyMap(toolResultBlock.getMetadata()));
|
||||
return converted;
|
||||
}
|
||||
if (block instanceof ImageBlock imageBlock) {
|
||||
return toMediaBlock(imageBlock, "image");
|
||||
}
|
||||
if (block instanceof AudioBlock audioBlock) {
|
||||
return toMediaBlock(audioBlock, "audio");
|
||||
}
|
||||
if (block instanceof VideoBlock videoBlock) {
|
||||
return toMediaBlock(videoBlock, "video");
|
||||
}
|
||||
AgentUnknownBlock converted = new AgentUnknownBlock();
|
||||
converted.setSourceClassName(block.getClass().getName());
|
||||
converted.setSourceTypeName(resolveTypeName(block));
|
||||
converted.setRawText(block.toString());
|
||||
converted.getRawMetadata().put("className", block.getClass().getName());
|
||||
return converted;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将运行时块转换为 AgentScope 块。
|
||||
*
|
||||
* @param block 运行时块
|
||||
* @return AgentScope 块
|
||||
*/
|
||||
public ContentBlock toContentBlock(AgentContentBlock block) {
|
||||
if (block == null) {
|
||||
return null;
|
||||
}
|
||||
if (block instanceof AgentTextBlock textBlock) {
|
||||
return TextBlock.builder().text(textBlock.getText()).build();
|
||||
}
|
||||
if (block instanceof AgentThinkingBlock thinkingBlock) {
|
||||
return ThinkingBlock.builder()
|
||||
.thinking(thinkingBlock.getThinking())
|
||||
.metadata(copyMap(thinkingBlock.getMetadata()))
|
||||
.build();
|
||||
}
|
||||
if (block instanceof AgentToolUseBlock toolUseBlock) {
|
||||
return ToolUseBlock.builder()
|
||||
.id(toolUseBlock.getId())
|
||||
.name(toolUseBlock.getName())
|
||||
.input(copyMap(toolUseBlock.getInput()))
|
||||
.content(toolUseBlock.getContent())
|
||||
.metadata(copyMap(toolUseBlock.getMetadata()))
|
||||
.build();
|
||||
}
|
||||
if (block instanceof AgentToolResultBlock toolResultBlock) {
|
||||
Map<String, Object> metadata = copyMap(toolResultBlock.getMetadata());
|
||||
metadata.put("suspended", toolResultBlock.isSuspended());
|
||||
return ToolResultBlock.of(
|
||||
toolResultBlock.getId(),
|
||||
toolResultBlock.getName(),
|
||||
toContentBlocks(toolResultBlock.getOutput()),
|
||||
metadata);
|
||||
}
|
||||
if (block instanceof AgentMediaBlock mediaBlock) {
|
||||
return toMediaBlock(mediaBlock);
|
||||
}
|
||||
if (block instanceof AgentUnknownBlock unknownBlock) {
|
||||
return toUnknownContentBlock(unknownBlock);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 AgentScope 块列表转换为运行时块列表。
|
||||
*
|
||||
* @param blocks AgentScope 块列表
|
||||
* @return 运行时块列表
|
||||
*/
|
||||
public List<AgentContentBlock> toBlocks(List<ContentBlock> blocks) {
|
||||
List<AgentContentBlock> converted = new ArrayList<>();
|
||||
if (blocks == null) {
|
||||
return converted;
|
||||
}
|
||||
for (ContentBlock block : blocks) {
|
||||
AgentContentBlock agentBlock = toAgentBlock(block);
|
||||
if (agentBlock != null) {
|
||||
converted.add(agentBlock);
|
||||
}
|
||||
}
|
||||
return converted;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将运行时块列表转换为 AgentScope 块列表。
|
||||
*
|
||||
* @param blocks 运行时块列表
|
||||
* @return AgentScope 块列表
|
||||
*/
|
||||
public List<ContentBlock> toContentBlocks(List<AgentContentBlock> blocks) {
|
||||
List<ContentBlock> converted = new ArrayList<>();
|
||||
if (blocks == null) {
|
||||
return converted;
|
||||
}
|
||||
for (AgentContentBlock block : blocks) {
|
||||
ContentBlock contentBlock = toContentBlock(block);
|
||||
if (contentBlock != null) {
|
||||
converted.add(contentBlock);
|
||||
}
|
||||
}
|
||||
return converted;
|
||||
}
|
||||
|
||||
private MsgRole toRole(AgentMessageRole role) {
|
||||
if (role == AgentMessageRole.ASSISTANT) {
|
||||
return MsgRole.ASSISTANT;
|
||||
}
|
||||
if (role == AgentMessageRole.SYSTEM) {
|
||||
return MsgRole.SYSTEM;
|
||||
}
|
||||
if (role == AgentMessageRole.TOOL) {
|
||||
return MsgRole.TOOL;
|
||||
}
|
||||
return MsgRole.USER;
|
||||
}
|
||||
|
||||
private AgentMessageRole toRole(MsgRole role) {
|
||||
if (role == MsgRole.ASSISTANT) {
|
||||
return AgentMessageRole.ASSISTANT;
|
||||
}
|
||||
if (role == MsgRole.SYSTEM) {
|
||||
return AgentMessageRole.SYSTEM;
|
||||
}
|
||||
if (role == MsgRole.TOOL) {
|
||||
return AgentMessageRole.TOOL;
|
||||
}
|
||||
return AgentMessageRole.USER;
|
||||
}
|
||||
|
||||
private Map<String, Object> copyMap(Map<String, Object> metadata) {
|
||||
return metadata == null ? new LinkedHashMap<>() : new LinkedHashMap<>(metadata);
|
||||
}
|
||||
|
||||
private ContentBlock toMediaBlock(AgentMediaBlock block) {
|
||||
if (block == null) {
|
||||
return null;
|
||||
}
|
||||
if ("audio".equalsIgnoreCase(block.getMediaKind())) {
|
||||
return AudioBlock.builder().source(mediaSource(block)).build();
|
||||
}
|
||||
if ("video".equalsIgnoreCase(block.getMediaKind())) {
|
||||
return VideoBlock.builder()
|
||||
.source(mediaSource(block))
|
||||
.fps(block.getFps())
|
||||
.maxFrames(block.getMaxFrames())
|
||||
.minPixels(block.getMinPixels())
|
||||
.maxPixels(block.getMaxPixels())
|
||||
.totalPixels(block.getTotalPixels())
|
||||
.build();
|
||||
}
|
||||
return ImageBlock.builder()
|
||||
.source(mediaSource(block))
|
||||
.minPixels(block.getMinPixels())
|
||||
.maxPixels(block.getMaxPixels())
|
||||
.build();
|
||||
}
|
||||
|
||||
private AgentContentBlock toMediaBlock(ContentBlock block, String mediaKind) {
|
||||
AgentMediaBlock converted = new AgentMediaBlock(mediaKind);
|
||||
if (block instanceof ImageBlock imageBlock) {
|
||||
readSource(imageBlock.getSource(), converted);
|
||||
converted.setMinPixels(imageBlock.getMinPixels());
|
||||
converted.setMaxPixels(imageBlock.getMaxPixels());
|
||||
} else if (block instanceof AudioBlock audioBlock) {
|
||||
readSource(audioBlock.getSource(), converted);
|
||||
} else if (block instanceof VideoBlock videoBlock) {
|
||||
readSource(videoBlock.getSource(), converted);
|
||||
converted.setFps(videoBlock.getFps());
|
||||
converted.setMaxFrames(videoBlock.getMaxFrames());
|
||||
converted.setMinPixels(videoBlock.getMinPixels());
|
||||
converted.setMaxPixels(videoBlock.getMaxPixels());
|
||||
converted.setTotalPixels(videoBlock.getTotalPixels());
|
||||
}
|
||||
return converted;
|
||||
}
|
||||
|
||||
private io.agentscope.core.message.Source mediaSource(AgentMediaBlock block) {
|
||||
if (block.getData() != null && !block.getData().isBlank()) {
|
||||
return Base64Source.builder()
|
||||
.mediaType(block.getMimeType())
|
||||
.data(block.getData())
|
||||
.build();
|
||||
}
|
||||
return URLSource.builder().url(block.getUrl()).build();
|
||||
}
|
||||
|
||||
private void readSource(io.agentscope.core.message.Source source, AgentMediaBlock block) {
|
||||
if (source instanceof Base64Source base64Source) {
|
||||
block.setMimeType(base64Source.getMediaType());
|
||||
block.setData(base64Source.getData());
|
||||
return;
|
||||
}
|
||||
if (source instanceof URLSource urlSource) {
|
||||
block.setUrl(urlSource.getUrl());
|
||||
}
|
||||
}
|
||||
|
||||
private ContentBlock toUnknownContentBlock(AgentUnknownBlock block) {
|
||||
String text = block.getRawText();
|
||||
if (text == null || text.isBlank()) {
|
||||
text = "[unsupported content block: " + block.getSourceTypeName() + "]";
|
||||
}
|
||||
return TextBlock.builder()
|
||||
.text(text)
|
||||
.build();
|
||||
}
|
||||
|
||||
private Instant parseTimestamp(String timestamp) {
|
||||
try {
|
||||
return Instant.parse(timestamp);
|
||||
} catch (Exception ignored) {
|
||||
// 继续尝试其他格式。
|
||||
}
|
||||
try {
|
||||
return OffsetDateTime.parse(timestamp).toInstant();
|
||||
} catch (Exception ignored) {
|
||||
// 继续尝试其他格式。
|
||||
}
|
||||
try {
|
||||
return Instant.ofEpochMilli(Long.parseLong(timestamp));
|
||||
} catch (Exception ignored) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private String resolveTypeName(ContentBlock block) {
|
||||
if (block instanceof TextBlock) {
|
||||
return "text";
|
||||
}
|
||||
if (block instanceof ThinkingBlock) {
|
||||
return "thinking";
|
||||
}
|
||||
if (block instanceof ToolUseBlock) {
|
||||
return "tool_use";
|
||||
}
|
||||
if (block instanceof ToolResultBlock) {
|
||||
return "tool_result";
|
||||
}
|
||||
if (block instanceof ImageBlock) {
|
||||
return "image";
|
||||
}
|
||||
if (block instanceof AudioBlock) {
|
||||
return "audio";
|
||||
}
|
||||
if (block instanceof VideoBlock) {
|
||||
return "video";
|
||||
}
|
||||
return block.getClass().getSimpleName();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,257 @@
|
||||
package com.easyagents.agent.runtime.agentscope;
|
||||
|
||||
import com.easyagents.agent.runtime.AgentRuntimeException;
|
||||
import com.easyagents.agent.runtime.model.AgentGenerationOptions;
|
||||
import com.easyagents.agent.runtime.model.AgentModelFactory;
|
||||
import com.easyagents.agent.runtime.model.AgentModelProviderType;
|
||||
import com.easyagents.agent.runtime.model.AgentModelSpec;
|
||||
import io.agentscope.core.formatter.openai.DeepSeekFormatter;
|
||||
import io.agentscope.core.formatter.openai.GLMFormatter;
|
||||
import io.agentscope.core.model.*;
|
||||
import io.agentscope.core.model.ollama.OllamaOptions;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 根据运行时模型声明创建 AgentScope 模型实例。
|
||||
*/
|
||||
public class AgentScopeModelFactory implements AgentModelFactory<Model> {
|
||||
|
||||
private static final String DEEPSEEK_BASE_URL = "https://api.deepseek.com";
|
||||
private static final String GLM_BASE_URL = "https://open.bigmodel.cn/api/paas/v4";
|
||||
private static final String MINIMAX_BASE_URL = "https://api.minimax.io/v1";
|
||||
private static final String MOONSHOT_BASE_URL = "https://api.moonshot.cn/v1";
|
||||
private static final String ARK_BASE_URL = "https://ark.cn-beijing.volces.com/api/v3";
|
||||
private static final String SILICONFLOW_BASE_URL = "https://api.siliconflow.cn/v1";
|
||||
|
||||
@Override
|
||||
public Model create(AgentModelSpec modelSpec, AgentGenerationOptions generationOptions) {
|
||||
if (modelSpec == null) {
|
||||
throw new AgentRuntimeException("Agent model spec is required.");
|
||||
}
|
||||
GenerateOptions options = toGenerateOptions(modelSpec, generationOptions);
|
||||
AgentModelProviderType providerType = modelSpec.getProviderType();
|
||||
if (providerType == AgentModelProviderType.OLLAMA) {
|
||||
return buildOllama(modelSpec, options);
|
||||
}
|
||||
if (providerType == AgentModelProviderType.OPENAI) {
|
||||
return buildOpenAi(modelSpec, options);
|
||||
}
|
||||
if (providerType == AgentModelProviderType.ANTHROPIC) {
|
||||
return buildAnthropic(modelSpec, options);
|
||||
}
|
||||
if (providerType == AgentModelProviderType.GEMINI) {
|
||||
return buildGemini(modelSpec, options);
|
||||
}
|
||||
if (providerType == AgentModelProviderType.DEEPSEEK) {
|
||||
return buildDeepSeek(modelSpec, options);
|
||||
}
|
||||
if (providerType == AgentModelProviderType.GLM) {
|
||||
return buildGlm(modelSpec, options);
|
||||
}
|
||||
if (providerType == AgentModelProviderType.MINIMAX) {
|
||||
return buildOpenAiCompatible(modelSpec, options, MINIMAX_BASE_URL);
|
||||
}
|
||||
if (providerType == AgentModelProviderType.MOONSHOT) {
|
||||
return buildOpenAiCompatible(modelSpec, options, MOONSHOT_BASE_URL);
|
||||
}
|
||||
if (providerType == AgentModelProviderType.ARK) {
|
||||
return buildOpenAiCompatible(modelSpec, options, ARK_BASE_URL);
|
||||
}
|
||||
if (providerType == AgentModelProviderType.SILICONFLOW) {
|
||||
return buildOpenAiCompatible(modelSpec, options, SILICONFLOW_BASE_URL);
|
||||
}
|
||||
if (providerType == AgentModelProviderType.DASHSCOPE) {
|
||||
return buildDashScope(modelSpec, generationOptions, options);
|
||||
}
|
||||
if (providerType == AgentModelProviderType.OPENAI_COMPATIBLE || providerType == AgentModelProviderType.CUSTOM) {
|
||||
return buildOpenAiCompatible(modelSpec, options, null);
|
||||
}
|
||||
throw new AgentRuntimeException("Unsupported agent model provider: " + providerType);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将中立参数转换为 AgentScope 参数。
|
||||
*
|
||||
* @param modelSpec 模型声明
|
||||
* @param options 中立参数
|
||||
* @return AgentScope 参数
|
||||
*/
|
||||
public GenerateOptions toGenerateOptions(AgentModelSpec modelSpec, AgentGenerationOptions options) {
|
||||
AgentGenerationOptions safeOptions = options == null ? new AgentGenerationOptions() : options;
|
||||
GenerateOptions.Builder builder = GenerateOptions.builder()
|
||||
.apiKey(modelSpec.getApiKey())
|
||||
.baseUrl(modelSpec.getBaseUrl())
|
||||
.endpointPath(modelSpec.getEndpointPath())
|
||||
.modelName(modelSpec.getModelName())
|
||||
.stream(safeOptions.getStream())
|
||||
.temperature(safeOptions.getTemperature())
|
||||
.topP(safeOptions.getTopP())
|
||||
.topK(safeOptions.getTopK())
|
||||
.maxTokens(safeOptions.getMaxTokens())
|
||||
.maxCompletionTokens(safeOptions.getMaxCompletionTokens())
|
||||
.thinkingBudget(safeOptions.getThinkingBudget())
|
||||
.reasoningEffort(safeOptions.getReasoningEffort());
|
||||
for (Map.Entry<String, Object> entry : safeOptions.getAdditionalBodyParams().entrySet()) {
|
||||
builder.additionalBodyParam(entry.getKey(), entry.getValue());
|
||||
}
|
||||
for (Map.Entry<String, String> entry : safeOptions.getAdditionalHeaders().entrySet()) {
|
||||
builder.additionalHeader(entry.getKey(), entry.getValue());
|
||||
}
|
||||
for (Map.Entry<String, String> entry : safeOptions.getAdditionalQueryParams().entrySet()) {
|
||||
builder.additionalQueryParam(entry.getKey(), entry.getValue());
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建 OpenAI 原生模型。
|
||||
*
|
||||
* @param modelSpec 模型声明
|
||||
* @param options 生成参数
|
||||
* @return 模型
|
||||
*/
|
||||
private Model buildOpenAi(AgentModelSpec modelSpec, GenerateOptions options) {
|
||||
return buildOpenAiCompatible(modelSpec, options, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过 OpenAI-compatible 协议构建模型。
|
||||
*
|
||||
* @param modelSpec 模型声明
|
||||
* @param options 生成参数
|
||||
* @param defaultBaseUrl 默认基础 URL
|
||||
* @return 模型
|
||||
*/
|
||||
private Model buildOpenAiCompatible(AgentModelSpec modelSpec, GenerateOptions options, String defaultBaseUrl) {
|
||||
OpenAIChatModel.Builder builder = OpenAIChatModel.builder()
|
||||
.apiKey(modelSpec.getApiKey())
|
||||
.modelName(modelSpec.getModelName())
|
||||
.baseUrl(resolveBaseUrl(modelSpec, defaultBaseUrl))
|
||||
.endpointPath(modelSpec.getEndpointPath())
|
||||
.stream(Boolean.TRUE.equals(options.getStream()))
|
||||
.generateOptions(options);
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建 Anthropic 模型。
|
||||
*
|
||||
* @param modelSpec 模型声明
|
||||
* @param options 生成参数
|
||||
* @return 模型
|
||||
*/
|
||||
private Model buildAnthropic(AgentModelSpec modelSpec, GenerateOptions options) {
|
||||
return AnthropicChatModel.builder()
|
||||
.apiKey(modelSpec.getApiKey())
|
||||
.modelName(modelSpec.getModelName())
|
||||
.baseUrl(modelSpec.getBaseUrl())
|
||||
.stream(Boolean.TRUE.equals(options.getStream()))
|
||||
.defaultOptions(options)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建 Gemini 模型。
|
||||
*
|
||||
* @param modelSpec 模型声明
|
||||
* @param options 生成参数
|
||||
* @return 模型
|
||||
*/
|
||||
private Model buildGemini(AgentModelSpec modelSpec, GenerateOptions options) {
|
||||
return GeminiChatModel.builder()
|
||||
.apiKey(modelSpec.getApiKey())
|
||||
.modelName(modelSpec.getModelName())
|
||||
.baseUrl(modelSpec.getBaseUrl())
|
||||
.streamEnabled(Boolean.TRUE.equals(options.getStream()))
|
||||
.defaultOptions(options)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过 OpenAI-compatible 协议构建 DeepSeek 模型。
|
||||
*
|
||||
* @param modelSpec 模型声明
|
||||
* @param options 生成参数
|
||||
* @return 模型
|
||||
*/
|
||||
private Model buildDeepSeek(AgentModelSpec modelSpec, GenerateOptions options) {
|
||||
OpenAIChatModel.Builder builder = OpenAIChatModel.builder()
|
||||
.apiKey(modelSpec.getApiKey())
|
||||
.modelName(modelSpec.getModelName())
|
||||
.baseUrl(resolveBaseUrl(modelSpec, DEEPSEEK_BASE_URL))
|
||||
.endpointPath(modelSpec.getEndpointPath())
|
||||
.stream(Boolean.TRUE.equals(options.getStream()))
|
||||
.formatter(new DeepSeekFormatter())
|
||||
.generateOptions(options);
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过 OpenAI-compatible 协议构建智谱 GLM 模型。
|
||||
*
|
||||
* @param modelSpec 模型声明
|
||||
* @param options 生成参数
|
||||
* @return 模型
|
||||
*/
|
||||
private Model buildGlm(AgentModelSpec modelSpec, GenerateOptions options) {
|
||||
OpenAIChatModel.Builder builder = OpenAIChatModel.builder()
|
||||
.apiKey(modelSpec.getApiKey())
|
||||
.modelName(modelSpec.getModelName())
|
||||
.baseUrl(resolveBaseUrl(modelSpec, GLM_BASE_URL))
|
||||
.endpointPath(modelSpec.getEndpointPath())
|
||||
.stream(Boolean.TRUE.equals(options.getStream()))
|
||||
.formatter(new GLMFormatter())
|
||||
.generateOptions(options);
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建 Ollama 模型。
|
||||
*
|
||||
* @param modelSpec 模型声明
|
||||
* @param options 生成参数
|
||||
* @return 模型
|
||||
*/
|
||||
private Model buildOllama(AgentModelSpec modelSpec, GenerateOptions options) {
|
||||
return OllamaChatModel.builder()
|
||||
.modelName(modelSpec.getModelName())
|
||||
.baseUrl(modelSpec.getBaseUrl())
|
||||
.defaultOptions(OllamaOptions.fromGenerateOptions(options))
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建 DashScope 模型。
|
||||
*
|
||||
* @param modelSpec 模型声明
|
||||
* @param generationOptions 中立生成参数
|
||||
* @param options AgentScope 生成参数
|
||||
* @return 模型
|
||||
*/
|
||||
private Model buildDashScope(AgentModelSpec modelSpec, AgentGenerationOptions generationOptions, GenerateOptions options) {
|
||||
Boolean thinkingEnabled = generationOptions == null ? null : generationOptions.getThinkingEnabled();
|
||||
return DashScopeChatModel.builder()
|
||||
.apiKey(modelSpec.getApiKey())
|
||||
.modelName(modelSpec.getModelName())
|
||||
.baseUrl(modelSpec.getBaseUrl())
|
||||
.stream(Boolean.TRUE.equals(options.getStream()))
|
||||
.enableThinking(thinkingEnabled)
|
||||
.defaultOptions(options)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 优先使用调用方传入的基础 URL,未传入时使用供应商默认地址。
|
||||
*
|
||||
* @param modelSpec 模型声明
|
||||
* @param defaultBaseUrl 默认基础 URL
|
||||
* @return 最终基础 URL
|
||||
*/
|
||||
private String resolveBaseUrl(AgentModelSpec modelSpec, String defaultBaseUrl) {
|
||||
if (modelSpec.getBaseUrl() == null || modelSpec.getBaseUrl().isBlank()) {
|
||||
return defaultBaseUrl;
|
||||
}
|
||||
return modelSpec.getBaseUrl();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,732 @@
|
||||
package com.easyagents.agent.runtime.agentscope;
|
||||
|
||||
import com.easyagents.agent.runtime.*;
|
||||
import com.easyagents.agent.runtime.event.AgentRuntimeEvent;
|
||||
import com.easyagents.agent.runtime.event.AgentRuntimeEventType;
|
||||
import com.easyagents.agent.runtime.hitl.AgentToolApprovalCoordinator;
|
||||
import com.easyagents.agent.runtime.hitl.AgentToolApprovalRejectedException;
|
||||
import com.easyagents.agent.runtime.hitl.AgentToolApprovalResponse;
|
||||
import com.easyagents.agent.runtime.knowledge.AgentKnowledgeSpec;
|
||||
import com.easyagents.agent.runtime.message.AgentKnowledgeReference;
|
||||
import com.easyagents.agent.runtime.message.AgentMessage;
|
||||
import com.easyagents.agent.runtime.skill.AgentSkillBinding;
|
||||
import com.easyagents.agent.runtime.skill.AgentSkillLoadCall;
|
||||
import com.easyagents.agent.runtime.skill.AgentSkillRuntimeContext;
|
||||
import com.easyagents.agent.runtime.tool.AgentToolInvoker;
|
||||
import com.easyagents.agent.runtime.tool.AgentToolSpec;
|
||||
import io.agentscope.core.ReActAgent;
|
||||
import io.agentscope.core.agent.Event;
|
||||
import io.agentscope.core.agent.EventType;
|
||||
import io.agentscope.core.agent.StreamOptions;
|
||||
import io.agentscope.core.memory.Memory;
|
||||
import io.agentscope.core.message.Msg;
|
||||
import io.agentscope.core.message.ToolResultBlock;
|
||||
import io.agentscope.core.message.ToolUseBlock;
|
||||
import io.agentscope.core.model.Model;
|
||||
import io.agentscope.core.rag.Knowledge;
|
||||
import io.agentscope.core.rag.RAGMode;
|
||||
import io.agentscope.core.rag.model.RetrieveConfig;
|
||||
import io.agentscope.core.session.Session;
|
||||
import io.agentscope.core.skill.SkillBox;
|
||||
import io.agentscope.core.tool.AgentTool;
|
||||
import io.agentscope.core.tool.Toolkit;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Sinks;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
/**
|
||||
* 基于 AgentScope 的 ReAct 运行时。
|
||||
*/
|
||||
public class AgentScopeReActRuntime implements AgentRuntime {
|
||||
|
||||
private final AgentScopeModelFactory modelFactory;
|
||||
private final AgentScopeToolAdapter toolAdapter;
|
||||
private final AgentScopeKnowledgeAdapter knowledgeAdapter;
|
||||
private final AgentScopeMemoryAdapter memoryAdapter;
|
||||
private final AgentScopeSkillAdapter skillAdapter;
|
||||
private final AgentScopeMessageAdapter messageAdapter;
|
||||
|
||||
/**
|
||||
* 使用默认适配器创建运行时。
|
||||
*/
|
||||
public AgentScopeReActRuntime() {
|
||||
this(new AgentScopeModelFactory(), new AgentScopeToolAdapter(), new AgentScopeKnowledgeAdapter(),
|
||||
new AgentScopeMemoryAdapter(), new AgentScopeSkillAdapter(), new AgentScopeMessageAdapter());
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用自定义适配器创建运行时。
|
||||
*
|
||||
* @param modelFactory 模型工厂
|
||||
* @param toolAdapter 工具适配器
|
||||
* @param knowledgeAdapter 知识库适配器
|
||||
* @param memoryAdapter 记忆适配器
|
||||
* @param skillAdapter Skill 适配器
|
||||
*/
|
||||
public AgentScopeReActRuntime(AgentScopeModelFactory modelFactory,
|
||||
AgentScopeToolAdapter toolAdapter,
|
||||
AgentScopeKnowledgeAdapter knowledgeAdapter,
|
||||
AgentScopeMemoryAdapter memoryAdapter,
|
||||
AgentScopeSkillAdapter skillAdapter,
|
||||
AgentScopeMessageAdapter messageAdapter) {
|
||||
this.modelFactory = modelFactory;
|
||||
this.toolAdapter = toolAdapter;
|
||||
this.knowledgeAdapter = knowledgeAdapter;
|
||||
this.memoryAdapter = memoryAdapter;
|
||||
this.skillAdapter = skillAdapter;
|
||||
this.messageAdapter = messageAdapter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<AgentRuntimeEvent> stream(AgentRunRequest request) {
|
||||
return Flux.defer(() -> {
|
||||
try {
|
||||
return start(request).stream();
|
||||
} catch (Throwable error) {
|
||||
return Flux.just(failed(request, error));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public AgentRunHandle start(AgentRunRequest request) {
|
||||
validate(request);
|
||||
AgentToolApprovalCoordinator approvalCoordinator = AgentToolApprovalCoordinator.enabled();
|
||||
Sinks.Many<AgentRuntimeEvent> sideEvents = Sinks.many().unicast().onBackpressureBuffer();
|
||||
AgentSkillRuntimeContext skillContext = AgentSkillRuntimeContext.from(request.getAgentDefinition().getSkillBoxSpec());
|
||||
StringBuilder finalText = new StringBuilder();
|
||||
AtomicReference<AgentMessage> finalMessage = new AtomicReference<>();
|
||||
Map<String, AgentKnowledgeReference> knowledgeReferences = new LinkedHashMap<>();
|
||||
AtomicBoolean cancelled = new AtomicBoolean(false);
|
||||
ReActAgent agent = buildAgent(request, sideEvents, approvalCoordinator, skillContext);
|
||||
loadSessionIfNeeded(request, agent);
|
||||
List<Msg> input = buildInput(request);
|
||||
StreamOptions streamOptions = StreamOptions.builder()
|
||||
.eventTypes(EventType.ALL)
|
||||
.incremental(true)
|
||||
.includeReasoningChunk(request.getAgentDefinition().getExecutionOptions().isReasoningEnabled())
|
||||
.includeReasoningResult(true)
|
||||
.includeActingChunk(true)
|
||||
.includeSummaryChunk(true)
|
||||
.includeSummaryResult(true)
|
||||
.build();
|
||||
Flux<AgentRuntimeEvent> mappedAgentEvents = agent.stream(input, streamOptions)
|
||||
.timeout(request.getAgentDefinition().getExecutionOptions().getTimeout())
|
||||
.flatMapIterable(event -> mapEvent(request, event, skillContext))
|
||||
.doOnNext(event -> updateFinalText(finalText, event))
|
||||
.doOnNext(event -> updateFinalMessage(finalMessage, event))
|
||||
.doOnNext(event -> updateKnowledgeReferences(knowledgeReferences, event))
|
||||
.doOnComplete(() -> saveSessionIfNeeded(request, agent))
|
||||
.doFinally(signalType -> sideEvents.tryEmitComplete())
|
||||
.concatWith(Flux.defer(() -> Flux.just(completed(request, finalText.toString(), finalMessage.get(), knowledgeReferences))));
|
||||
Flux<AgentRuntimeEvent> stream = Flux.concat(Flux.just(started(request)), Flux.merge(sideEvents.asFlux(), mappedAgentEvents))
|
||||
.doOnNext(event -> request.getConversationRecorder().record(request, event))
|
||||
.onErrorResume(error -> {
|
||||
if (error instanceof AgentToolApprovalRejectedException approvalRejectedException) {
|
||||
// 工具执行被拒绝
|
||||
request.setCancelReason(approvalRejectedException.getRejectReason());
|
||||
saveSessionIfNeeded(request, agent);
|
||||
if (cancelled.compareAndSet(false, true)) {
|
||||
return Flux.just(cancelled(request))
|
||||
.doOnNext(event -> request.getConversationRecorder().record(request, event));
|
||||
}
|
||||
return Flux.empty();
|
||||
}
|
||||
return Flux.just(failed(request, error))
|
||||
.doOnNext(event -> request.getConversationRecorder().record(request, event));
|
||||
});
|
||||
return new AgentRunHandle() {
|
||||
private final AtomicBoolean cancelled = new AtomicBoolean(false);
|
||||
|
||||
@Override
|
||||
public Flux<AgentRuntimeEvent> stream() {
|
||||
return stream.doOnCancel(() -> cancelInternal(agent, approvalCoordinator, sideEvents, request, cancelled));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancel() {
|
||||
cancelInternal(agent, approvalCoordinator, sideEvents, request, cancelled);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void submitToolApproval(AgentToolApprovalResponse response) {
|
||||
approvalCoordinator.submit(response);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 为单次请求构建 ReActAgent。
|
||||
*
|
||||
* @param request 运行请求
|
||||
* @param sideEvents 旁路事件 sink
|
||||
* @return ReActAgent 实例
|
||||
*/
|
||||
public ReActAgent buildAgent(AgentRunRequest request, Sinks.Many<AgentRuntimeEvent> sideEvents) {
|
||||
return buildAgent(request, sideEvents, AgentToolApprovalCoordinator.disabled());
|
||||
}
|
||||
|
||||
/**
|
||||
* 为单次请求构建 ReActAgent。
|
||||
*
|
||||
* @param request 运行请求
|
||||
* @param sideEvents 旁路事件 sink
|
||||
* @param approvalCoordinator 审批协调器
|
||||
* @return ReActAgent 实例
|
||||
*/
|
||||
public ReActAgent buildAgent(AgentRunRequest request,
|
||||
Sinks.Many<AgentRuntimeEvent> sideEvents,
|
||||
AgentToolApprovalCoordinator approvalCoordinator) {
|
||||
return buildAgent(request, sideEvents, approvalCoordinator,
|
||||
AgentSkillRuntimeContext.from(request.getAgentDefinition().getSkillBoxSpec()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 为单次请求构建 ReActAgent。
|
||||
*
|
||||
* @param request 运行请求
|
||||
* @param sideEvents 旁路事件 sink
|
||||
* @param approvalCoordinator 审批协调器
|
||||
* @param skillContext Skill 运行时上下文
|
||||
* @return ReActAgent 实例
|
||||
*/
|
||||
public ReActAgent buildAgent(AgentRunRequest request,
|
||||
Sinks.Many<AgentRuntimeEvent> sideEvents,
|
||||
AgentToolApprovalCoordinator approvalCoordinator,
|
||||
AgentSkillRuntimeContext skillContext) {
|
||||
AgentDefinition definition = request.getAgentDefinition();
|
||||
Model model = modelFactory.create(definition.getModelSpec(), definition.getGenerationOptions());
|
||||
Toolkit toolkit = new Toolkit();
|
||||
Map<String, List<AgentTool>> skillTools = buildToolkit(request, toolkit, sideEvents, approvalCoordinator, skillContext);
|
||||
Memory memory = memoryAdapter.createMemory(request.getMemorySnapshot(), definition.getMemoryPolicy(), model);
|
||||
Knowledge knowledge = knowledgeAdapter.createAggregateKnowledge(request, sideEvents);
|
||||
SkillBox skillBox = skillAdapter.createSkillBox(definition.getSkillBoxSpec(), toolkit, skillTools);
|
||||
// 构建ReActAgent
|
||||
ReActAgent.Builder builder = ReActAgent.builder()
|
||||
.name(definition.getAgentName())
|
||||
.description(definition.getDescription())
|
||||
.sysPrompt(definition.getSystemPrompt())
|
||||
.model(model)
|
||||
.toolkit(toolkit)
|
||||
.memory(memory)
|
||||
.maxIters(definition.getExecutionOptions().getMaxIters())
|
||||
.generateOptions(modelFactory.toGenerateOptions(definition.getModelSpec(), definition.getGenerationOptions()))
|
||||
.hook(new AgentScopeEventHook(request))
|
||||
.statePersistence(AgentScopeSessionAdapter.toStatePersistence(definition.getPersistencePolicy()));
|
||||
if (knowledge != null) {
|
||||
builder.knowledge(knowledge)
|
||||
.ragMode(RAGMode.AGENTIC)
|
||||
.retrieveConfig(defaultRetrieveConfig(definition));
|
||||
}
|
||||
if (skillBox != null) {
|
||||
builder.skillBox(skillBox);
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
private Toolkit buildToolkit(AgentRunRequest request, Sinks.Many<AgentRuntimeEvent> sideEvents) {
|
||||
return buildToolkit(request, sideEvents, AgentToolApprovalCoordinator.disabled());
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建工具箱。
|
||||
*
|
||||
* @param request 运行请求
|
||||
* @param sideEvents 旁路事件 sink
|
||||
* @param approvalCoordinator 审批协调器
|
||||
* @return 工具箱
|
||||
*/
|
||||
private Toolkit buildToolkit(AgentRunRequest request,
|
||||
Sinks.Many<AgentRuntimeEvent> sideEvents,
|
||||
AgentToolApprovalCoordinator approvalCoordinator) {
|
||||
Toolkit toolkit = new Toolkit();
|
||||
buildToolkit(request, toolkit, sideEvents, approvalCoordinator,
|
||||
AgentSkillRuntimeContext.from(request.getAgentDefinition().getSkillBoxSpec()));
|
||||
return toolkit;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建工具箱并返回 Skill 绑定工具。
|
||||
*
|
||||
* @param request 运行请求
|
||||
* @param toolkit 工具箱
|
||||
* @param sideEvents 旁路事件 sink
|
||||
* @param approvalCoordinator 审批协调器
|
||||
* @param skillContext Skill 运行时上下文
|
||||
* @return 按 Skill ID 分组的工具
|
||||
*/
|
||||
private Map<String, List<AgentTool>> buildToolkit(AgentRunRequest request,
|
||||
Toolkit toolkit,
|
||||
Sinks.Many<AgentRuntimeEvent> sideEvents,
|
||||
AgentToolApprovalCoordinator approvalCoordinator,
|
||||
AgentSkillRuntimeContext skillContext) {
|
||||
Map<String, List<AgentTool>> skillTools = new LinkedHashMap<>();
|
||||
if (!request.getAgentDefinition().getExecutionOptions().isToolCallingEnabled()) {
|
||||
return skillTools;
|
||||
}
|
||||
for (AgentToolSpec toolSpec : request.getAgentDefinition().getToolSpecs()) {
|
||||
AgentToolInvoker invoker = request.getToolInvokers().get(toolSpec.getName());
|
||||
AgentSkillBinding skillBinding = skillContext.getToolBinding(toolSpec.getName());
|
||||
AgentTool agentTool = toolAdapter.adapt(toolSpec, invoker, request, approvalCoordinator, sideEvents,
|
||||
skillContext, skillBinding);
|
||||
if (skillBinding == null) {
|
||||
toolkit.registerAgentTool(agentTool);
|
||||
} else {
|
||||
skillTools.computeIfAbsent(skillBinding.getSkillId(), key -> new ArrayList<>()).add(agentTool);
|
||||
}
|
||||
}
|
||||
return skillTools;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建当前轮的 AgentScope 输入消息。
|
||||
*
|
||||
* @param request 运行请求
|
||||
* @return 输入消息
|
||||
*/
|
||||
private List<Msg> buildInput(AgentRunRequest request) {
|
||||
List<Msg> input = new ArrayList<>();
|
||||
AgentMessage userMessage = request.getUserMessage();
|
||||
if (userMessage != null) {
|
||||
input.add(messageAdapter.toMsg(userMessage));
|
||||
}
|
||||
return input;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将单个 AgentScope 事件映射为运行时事件。
|
||||
*
|
||||
* @param request 运行请求
|
||||
* @param event AgentScope 事件
|
||||
* @return 运行时事件
|
||||
*/
|
||||
private List<AgentRuntimeEvent> mapEvent(AgentRunRequest request, Event event) {
|
||||
return mapEvent(request, event, AgentSkillRuntimeContext.from(request.getAgentDefinition().getSkillBoxSpec()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 将单个 AgentScope 事件映射为运行时事件。
|
||||
*
|
||||
* @param request 运行请求
|
||||
* @param event AgentScope 事件
|
||||
* @param skillContext Skill 运行时上下文
|
||||
* @return 运行时事件
|
||||
*/
|
||||
private List<AgentRuntimeEvent> mapEvent(AgentRunRequest request, Event event, AgentSkillRuntimeContext skillContext) {
|
||||
List<AgentRuntimeEvent> events = new ArrayList<>();
|
||||
if (event == null) {
|
||||
return events;
|
||||
}
|
||||
if (event.getType() == EventType.REASONING) {
|
||||
events.addAll(mapSkillCalls(request, event, skillContext));
|
||||
AgentRuntimeEvent runtimeEvent = base(request, AgentRuntimeEventType.REASONING_DELTA);
|
||||
runtimeEvent.setMessageId(event.getMessageId());
|
||||
runtimeEvent.getPayload().put("text", event.getMessage() == null ? null : event.getMessage().getTextContent());
|
||||
runtimeEvent.getPayload().put("last", event.isLast());
|
||||
events.add(runtimeEvent);
|
||||
return events;
|
||||
}
|
||||
if (event.getType() == EventType.TOOL_RESULT) {
|
||||
events.addAll(mapSkillResults(request, event, skillContext));
|
||||
return events;
|
||||
}
|
||||
if (event.getType() == EventType.AGENT_RESULT) {
|
||||
AgentRuntimeEvent runtimeEvent = base(request, AgentRuntimeEventType.MESSAGE_DELTA);
|
||||
runtimeEvent.setMessageId(event.getMessageId());
|
||||
runtimeEvent.getPayload().put("text", event.getMessage() == null ? null : event.getMessage().getTextContent());
|
||||
runtimeEvent.getPayload().put("last", event.isLast());
|
||||
if (event.isLast() && event.getMessage() != null) {
|
||||
runtimeEvent.setMessage(messageAdapter.toAgentMessage(event.getMessage()));
|
||||
}
|
||||
events.add(runtimeEvent);
|
||||
return events;
|
||||
}
|
||||
return events;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从推理消息中识别 Skill 加载调用。
|
||||
*
|
||||
* @param request 运行请求
|
||||
* @param event AgentScope 事件
|
||||
* @param skillContext Skill 运行时上下文
|
||||
* @return Skill 调用事件
|
||||
*/
|
||||
private List<AgentRuntimeEvent> mapSkillCalls(AgentRunRequest request,
|
||||
Event event,
|
||||
AgentSkillRuntimeContext skillContext) {
|
||||
List<AgentRuntimeEvent> events = new ArrayList<>();
|
||||
if (event.getMessage() == null) {
|
||||
return events;
|
||||
}
|
||||
for (ToolUseBlock block : event.getMessage().getContentBlocks(ToolUseBlock.class)) {
|
||||
if (!skillContext.isSkillLoadTool(block.getName())) {
|
||||
continue;
|
||||
}
|
||||
AgentSkillLoadCall call = skillContext.rememberLoadCall(block.getId(), block.getInput());
|
||||
if (!skillContext.markLoadCallEmitted(block.getId())) {
|
||||
continue;
|
||||
}
|
||||
AgentRuntimeEvent runtimeEvent = base(request, AgentRuntimeEventType.SKILL_CALL);
|
||||
runtimeEvent.setMessageId(event.getMessageId());
|
||||
runtimeEvent.setToolCallId(block.getId());
|
||||
appendSkillLoadPayload(runtimeEvent, call);
|
||||
runtimeEvent.getPayload().put("toolName", block.getName());
|
||||
runtimeEvent.getPayload().put("input", block.getInput());
|
||||
runtimeEvent.getPayload().put("status", "RUNNING");
|
||||
events.add(runtimeEvent);
|
||||
}
|
||||
return events;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从工具结果中识别 Skill 加载结果。
|
||||
*
|
||||
* @param request 运行请求
|
||||
* @param event AgentScope 事件
|
||||
* @param skillContext Skill 运行时上下文
|
||||
* @return Skill 结果事件
|
||||
*/
|
||||
private List<AgentRuntimeEvent> mapSkillResults(AgentRunRequest request,
|
||||
Event event,
|
||||
AgentSkillRuntimeContext skillContext) {
|
||||
List<AgentRuntimeEvent> events = new ArrayList<>();
|
||||
if (event.getMessage() == null) {
|
||||
return events;
|
||||
}
|
||||
for (ToolResultBlock block : event.getMessage().getContentBlocks(ToolResultBlock.class)) {
|
||||
if (!skillContext.isSkillLoadTool(block.getName())) {
|
||||
continue;
|
||||
}
|
||||
AgentSkillLoadCall call = skillContext.removeLoadCall(block.getId());
|
||||
AgentRuntimeEvent runtimeEvent = base(request, skillResultType(block));
|
||||
if (runtimeEvent.getEventType() == AgentRuntimeEventType.SKILL_RESULT && call != null) {
|
||||
skillContext.activateSkill(call.getSkillId());
|
||||
}
|
||||
runtimeEvent.setMessageId(event.getMessageId());
|
||||
runtimeEvent.setToolCallId(block.getId());
|
||||
appendSkillLoadPayload(runtimeEvent, call);
|
||||
runtimeEvent.getPayload().put("toolName", block.getName());
|
||||
runtimeEvent.getPayload().put("text", block.toString());
|
||||
runtimeEvent.getPayload().put("status", runtimeEvent.getEventType() == AgentRuntimeEventType.SKILL_RESULT ? "SUCCESS" : "FAILED");
|
||||
runtimeEvent.getPayload().put("success", runtimeEvent.getEventType() == AgentRuntimeEventType.SKILL_RESULT);
|
||||
runtimeEvent.getPayload().put("suspended", block.isSuspended());
|
||||
runtimeEvent.getMetadata().putAll(block.getMetadata() == null ? new LinkedHashMap<>() : block.getMetadata());
|
||||
events.add(runtimeEvent);
|
||||
}
|
||||
return events;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断 Skill 结果事件类型。
|
||||
*
|
||||
* @param block 工具结果块
|
||||
* @return Skill 事件类型
|
||||
*/
|
||||
private AgentRuntimeEventType skillResultType(ToolResultBlock block) {
|
||||
Object success = block.getMetadata() == null ? null : block.getMetadata().get("success");
|
||||
if (Boolean.FALSE.equals(success)) {
|
||||
return AgentRuntimeEventType.SKILL_FAILED;
|
||||
}
|
||||
String text = block.toString();
|
||||
if (text != null && text.toLowerCase().contains("error")) {
|
||||
return AgentRuntimeEventType.SKILL_FAILED;
|
||||
}
|
||||
return AgentRuntimeEventType.SKILL_RESULT;
|
||||
}
|
||||
|
||||
/**
|
||||
* 追加 Skill 加载事件载荷。
|
||||
*
|
||||
* @param event 运行时事件
|
||||
* @param call Skill 加载调用
|
||||
*/
|
||||
private void appendSkillLoadPayload(AgentRuntimeEvent event, AgentSkillLoadCall call) {
|
||||
if (call == null) {
|
||||
return;
|
||||
}
|
||||
event.getPayload().put("skillId", call.getSkillId());
|
||||
event.getPayload().put("skillName", call.getSkillName());
|
||||
event.getPayload().put("skillBoxId", call.getSkillBoxId());
|
||||
event.getPayload().put("path", call.getPath());
|
||||
event.getMetadata().put("skillId", call.getSkillId());
|
||||
event.getMetadata().put("skillName", call.getSkillName());
|
||||
event.getMetadata().put("skillBoxId", call.getSkillBoxId());
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建聚合知识库的默认检索配置。
|
||||
*
|
||||
* @param definition 智能体定义
|
||||
* @return 检索配置
|
||||
*/
|
||||
private RetrieveConfig defaultRetrieveConfig(AgentDefinition definition) {
|
||||
int limit = definition.getKnowledgeSpecs().stream()
|
||||
.mapToInt(AgentKnowledgeSpec::getLimit)
|
||||
.filter(value -> value > 0)
|
||||
.sum();
|
||||
double scoreThreshold = definition.getKnowledgeSpecs().stream()
|
||||
.mapToDouble(AgentKnowledgeSpec::getScoreThreshold)
|
||||
.filter(value -> value > 0D)
|
||||
.min()
|
||||
.orElse(0D);
|
||||
return RetrieveConfig.builder()
|
||||
.limit(limit <= 0 ? 5 : limit)
|
||||
.scoreThreshold(scoreThreshold)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 从消息增量事件中收集最终助手文本。
|
||||
*
|
||||
* @param finalText 最终文本引用
|
||||
* @param event 运行时事件
|
||||
*/
|
||||
private void updateFinalText(StringBuilder finalText, AgentRuntimeEvent event) {
|
||||
if (event.getEventType() == AgentRuntimeEventType.MESSAGE_DELTA) {
|
||||
Object text = event.getPayload().get("text");
|
||||
if (text != null) {
|
||||
String chunk = String.valueOf(text);
|
||||
if (Boolean.TRUE.equals(event.getPayload().get("last")) && finalText.length() > chunk.length()) {
|
||||
finalText.setLength(0);
|
||||
}
|
||||
finalText.append(chunk);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从最终消息事件中收集结构化助手消息。
|
||||
*
|
||||
* @param finalMessage 最终消息引用
|
||||
* @param event 运行时事件
|
||||
*/
|
||||
private void updateFinalMessage(AtomicReference<AgentMessage> finalMessage, AgentRuntimeEvent event) {
|
||||
if (event.getEventType() == AgentRuntimeEventType.MESSAGE_DELTA
|
||||
&& Boolean.TRUE.equals(event.getPayload().get("last"))
|
||||
&& event.getMessage() != null) {
|
||||
finalMessage.set(event.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验请求的最小必要字段。
|
||||
*
|
||||
* @param request 运行请求
|
||||
*/
|
||||
private void validate(AgentRunRequest request) {
|
||||
if (request == null) {
|
||||
throw new AgentRuntimeException("Agent run request is required.");
|
||||
}
|
||||
if (request.getAgentDefinition() == null) {
|
||||
throw new AgentRuntimeException("Agent definition is required.");
|
||||
}
|
||||
if (request.getAgentDefinition().getModelSpec() == null) {
|
||||
throw new AgentRuntimeException("Agent model spec is required.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 启用时加载 AgentScope 会话状态。
|
||||
*
|
||||
* @param request 运行请求
|
||||
* @param agent AgentScope 智能体
|
||||
*/
|
||||
private void loadSessionIfNeeded(AgentRunRequest request, ReActAgent agent) {
|
||||
if (request.getAgentDefinition().getPersistencePolicy().isEnabled()
|
||||
&& request.getAgentDefinition().getPersistencePolicy().isSessionManaged()
|
||||
&& request.getSessionId() != null) {
|
||||
Session session = new AgentScopeSessionAdapter(request.getSessionStore());
|
||||
agent.loadIfExists(session, AgentScopeSessionAdapter.sessionKey(request.getSessionId()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 启用时保存 AgentScope 会话状态。
|
||||
*
|
||||
* @param request 运行请求
|
||||
* @param agent AgentScope 智能体
|
||||
*/
|
||||
private void saveSessionIfNeeded(AgentRunRequest request, ReActAgent agent) {
|
||||
if (request.getAgentDefinition().getPersistencePolicy().isEnabled()
|
||||
&& request.getAgentDefinition().getPersistencePolicy().isSessionManaged()
|
||||
&& request.getSessionId() != null) {
|
||||
Session session = new AgentScopeSessionAdapter(request.getSessionStore());
|
||||
agent.saveTo(session, AgentScopeSessionAdapter.sessionKey(request.getSessionId()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建开始事件。
|
||||
*
|
||||
* @param request 运行请求
|
||||
* @return 开始事件
|
||||
*/
|
||||
private AgentRuntimeEvent started(AgentRunRequest request) {
|
||||
AgentRuntimeEvent event = base(request, AgentRuntimeEventType.STARTED);
|
||||
event.getPayload().put("requestId", request.getRequestId());
|
||||
return event;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建完成事件。
|
||||
*
|
||||
* @param request 运行请求
|
||||
* @param text 最终文本
|
||||
* @return 完成事件
|
||||
*/
|
||||
private AgentRuntimeEvent completed(AgentRunRequest request, String text, AgentMessage message) {
|
||||
AgentRuntimeEvent event = base(request, AgentRuntimeEventType.COMPLETED);
|
||||
event.getPayload().put("text", text);
|
||||
event.setMessage(message);
|
||||
return event;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建完成事件,并附带知识库引用。
|
||||
*
|
||||
* @param request 运行请求
|
||||
* @param text 最终文本
|
||||
* @param message 最终消息
|
||||
* @param knowledgeReferences 知识库引用
|
||||
* @return 完成事件
|
||||
*/
|
||||
private AgentRuntimeEvent completed(AgentRunRequest request,
|
||||
String text,
|
||||
AgentMessage message,
|
||||
Map<String, AgentKnowledgeReference> knowledgeReferences) {
|
||||
if (message != null && knowledgeReferences != null && !knowledgeReferences.isEmpty()) {
|
||||
message.setKnowledgeReferences(new ArrayList<>(knowledgeReferences.values()));
|
||||
}
|
||||
return completed(request, text, message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建失败事件。
|
||||
*
|
||||
* @param request 运行请求
|
||||
* @param error 错误
|
||||
* @return 失败事件
|
||||
*/
|
||||
private AgentRuntimeEvent failed(AgentRunRequest request, Throwable error) {
|
||||
AgentRuntimeEvent event = request == null || request.getAgentDefinition() == null
|
||||
? AgentRuntimeEvent.of(AgentRuntimeEventType.FAILED)
|
||||
: base(request, AgentRuntimeEventType.FAILED);
|
||||
event.getPayload().put("message", error.getMessage());
|
||||
event.getPayload().put("errorType", error.getClass().getName());
|
||||
return event;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建取消事件。
|
||||
*
|
||||
* @param request 运行请求
|
||||
* @return 取消事件
|
||||
*/
|
||||
private AgentRuntimeEvent cancelled(AgentRunRequest request) {
|
||||
AgentRuntimeEvent event = base(request, AgentRuntimeEventType.CANCELLED);
|
||||
event.getPayload().put("reason", request.getCancelReason());
|
||||
return event;
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消本次运行并避免重复发出取消事件。
|
||||
*
|
||||
* @param agent AgentScope 智能体
|
||||
* @param sideEvents 旁路事件 sink
|
||||
* @param request 运行请求
|
||||
* @param cancelled 取消标记
|
||||
*/
|
||||
private void cancelInternal(ReActAgent agent,
|
||||
AgentToolApprovalCoordinator approvalCoordinator,
|
||||
Sinks.Many<AgentRuntimeEvent> sideEvents,
|
||||
AgentRunRequest request,
|
||||
java.util.concurrent.atomic.AtomicBoolean cancelled) {
|
||||
if (!cancelled.compareAndSet(false, true)) {
|
||||
return;
|
||||
}
|
||||
approvalCoordinator.cancelAll(request.getCancelReason() == null ? "运行已取消" : request.getCancelReason());
|
||||
saveSessionIfNeeded(request, agent);
|
||||
agent.interrupt();
|
||||
sideEvents.tryEmitNext(cancelled(request));
|
||||
}
|
||||
|
||||
/**
|
||||
* 收集知识库引用,供最终消息 metadata 使用。
|
||||
*
|
||||
* @param knowledgeReferences 知识库引用
|
||||
* @param event 运行时事件
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private void updateKnowledgeReferences(Map<String, AgentKnowledgeReference> knowledgeReferences, AgentRuntimeEvent event) {
|
||||
if (event == null || event.getEventType() != AgentRuntimeEventType.KNOWLEDGE_RETRIEVAL) {
|
||||
return;
|
||||
}
|
||||
Object documentsObject = event.getPayload().get("documents");
|
||||
if (!(documentsObject instanceof List<?> documents) || documents.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
Object knowledgeId = event.getPayload().get("knowledgeId");
|
||||
Object knowledgeName = event.getPayload().get("knowledgeName");
|
||||
for (Object documentObject : documents) {
|
||||
if (!(documentObject instanceof Map<?, ?> documentMap)) {
|
||||
continue;
|
||||
}
|
||||
AgentKnowledgeReference reference = new AgentKnowledgeReference();
|
||||
reference.setKnowledgeId(stringValue(knowledgeId));
|
||||
reference.setKnowledgeName(stringValue(knowledgeName));
|
||||
reference.setDocumentId(stringValue(documentMap.get("documentId")));
|
||||
reference.setDocumentName(stringValue(documentMap.get("documentName")));
|
||||
reference.setChunkId(stringValue(documentMap.get("chunkId")));
|
||||
reference.setSourceUri(stringValue(documentMap.get("sourceUri")));
|
||||
reference.setScore(documentMap.get("score") instanceof Number score ? score.doubleValue() : null);
|
||||
Object metadata = documentMap.get("metadata");
|
||||
reference.setMetadata(metadata instanceof Map<?, ?> map ? new LinkedHashMap<>((Map<String, Object>) map) : new LinkedHashMap<>());
|
||||
String key = String.valueOf(knowledgeId) + "|" + String.valueOf(documentMap.get("documentId")) + "|" + String.valueOf(documentMap.get("chunkId"));
|
||||
knowledgeReferences.putIfAbsent(key, reference);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将对象转换为字符串。
|
||||
*
|
||||
* @param value 值
|
||||
* @return 字符串值
|
||||
*/
|
||||
private String stringValue(Object value) {
|
||||
return value == null ? null : String.valueOf(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建携带通用标识的运行时事件。
|
||||
*
|
||||
* @param request 运行请求
|
||||
* @param type 事件类型
|
||||
* @return 事件
|
||||
*/
|
||||
private AgentRuntimeEvent base(AgentRunRequest request, AgentRuntimeEventType type) {
|
||||
AgentRuntimeEvent event = AgentRuntimeEvent.of(type);
|
||||
event.setTraceId(request.getTraceId());
|
||||
event.setSessionId(request.getSessionId());
|
||||
event.setAgentId(request.getAgentDefinition().getAgentId());
|
||||
event.getMetadata().put("requestId", request.getRequestId());
|
||||
event.getMetadata().putAll(nullToEmpty(request.getMetadata()));
|
||||
return event;
|
||||
}
|
||||
|
||||
/**
|
||||
* 当来源元数据为空时返回空 Map。
|
||||
*
|
||||
* @param map 来源 Map
|
||||
* @return 非空 Map
|
||||
*/
|
||||
private Map<String, Object> nullToEmpty(Map<String, Object> map) {
|
||||
return map == null ? new LinkedHashMap<>() : map;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
package com.easyagents.agent.runtime.agentscope;
|
||||
|
||||
import com.easyagents.agent.runtime.persistence.AgentPersistencePolicy;
|
||||
import com.easyagents.agent.runtime.persistence.AgentRuntimeState;
|
||||
import com.easyagents.agent.runtime.persistence.AgentSessionStore;
|
||||
import io.agentscope.core.session.Session;
|
||||
import io.agentscope.core.state.SessionKey;
|
||||
import io.agentscope.core.state.State;
|
||||
import io.agentscope.core.state.StatePersistence;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 将 AgentScope Session 桥接到运行时 AgentSessionStore。
|
||||
*/
|
||||
public class AgentScopeSessionAdapter implements Session {
|
||||
|
||||
private final AgentSessionStore sessionStore;
|
||||
|
||||
/**
|
||||
* 创建适配器。
|
||||
*
|
||||
* @param sessionStore 运行时会话存储
|
||||
*/
|
||||
public AgentScopeSessionAdapter(AgentSessionStore sessionStore) {
|
||||
this.sessionStore = sessionStore;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save(SessionKey sessionKey, String name, State state) {
|
||||
sessionStore.save(toKey(sessionKey), name, AgentRuntimeState.of(name, state));
|
||||
}
|
||||
|
||||
/**
|
||||
* 将状态列表保存到运行时存储。
|
||||
*
|
||||
* @param sessionKey 会话键
|
||||
* @param name 状态名称
|
||||
* @param states 状态列表
|
||||
*/
|
||||
@Override
|
||||
public void save(SessionKey sessionKey, String name, List<? extends State> states) {
|
||||
List<AgentRuntimeState> converted = new ArrayList<>();
|
||||
if (states != null) {
|
||||
for (State state : states) {
|
||||
converted.add(AgentRuntimeState.of(name, state));
|
||||
}
|
||||
}
|
||||
sessionStore.saveList(toKey(sessionKey), name, converted);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从运行时存储获取单个指定类型的状态。
|
||||
*
|
||||
* @param sessionKey 会话键
|
||||
* @param name 状态名称
|
||||
* @param clazz 状态类型
|
||||
* @param <T> 状态类型
|
||||
* @return 可选状态
|
||||
*/
|
||||
@Override
|
||||
public <T extends State> Optional<T> get(SessionKey sessionKey, String name, Class<T> clazz) {
|
||||
return sessionStore.get(toKey(sessionKey), name)
|
||||
.map(AgentRuntimeState::getValue)
|
||||
.filter(clazz::isInstance)
|
||||
.map(clazz::cast);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从运行时存储获取指定类型的状态列表。
|
||||
*
|
||||
* @param sessionKey 会话键
|
||||
* @param name 状态名称
|
||||
* @param clazz 状态类型
|
||||
* @param <T> 状态类型
|
||||
* @return 状态列表
|
||||
*/
|
||||
@Override
|
||||
public <T extends State> List<T> getList(SessionKey sessionKey, String name, Class<T> clazz) {
|
||||
return sessionStore.getList(toKey(sessionKey), name).stream()
|
||||
.map(AgentRuntimeState::getValue)
|
||||
.filter(clazz::isInstance)
|
||||
.map(clazz::cast)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回会话键是否存在。
|
||||
*
|
||||
* @param sessionKey 会话键
|
||||
* @return 存在时为 true
|
||||
*/
|
||||
@Override
|
||||
public boolean exists(SessionKey sessionKey) {
|
||||
return sessionStore.exists(toKey(sessionKey));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除会话键。
|
||||
*
|
||||
* @param sessionKey 会话键
|
||||
*/
|
||||
@Override
|
||||
public void delete(SessionKey sessionKey) {
|
||||
sessionStore.delete(toKey(sessionKey));
|
||||
}
|
||||
|
||||
/**
|
||||
* 列出已存储的会话键。
|
||||
*
|
||||
* @return 会话键列表
|
||||
*/
|
||||
@Override
|
||||
public Set<SessionKey> listSessionKeys() {
|
||||
return sessionStore.listSessionKeys().stream()
|
||||
.map(RuntimeSessionKey::new)
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
/**
|
||||
* 将持久化策略转换为 AgentScope 持久化标记。
|
||||
*
|
||||
* @param policy 运行时策略
|
||||
* @return AgentScope 状态持久化配置
|
||||
*/
|
||||
public static StatePersistence toStatePersistence(AgentPersistencePolicy policy) {
|
||||
if (policy == null || !policy.isEnabled()) {
|
||||
return StatePersistence.none();
|
||||
}
|
||||
return StatePersistence.builder()
|
||||
.memoryManaged(policy.isMemoryManaged())
|
||||
.toolkitManaged(policy.isToolkitManaged())
|
||||
.planNotebookManaged(policy.isPlanNotebookManaged())
|
||||
.statefulToolsManaged(policy.isStatefulToolsManaged())
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建会话键。
|
||||
*
|
||||
* @param value 键值
|
||||
* @return 会话键
|
||||
*/
|
||||
public static SessionKey sessionKey(String value) {
|
||||
return new RuntimeSessionKey(value);
|
||||
}
|
||||
|
||||
private String toKey(SessionKey sessionKey) {
|
||||
return sessionKey == null ? "" : sessionKey.toIdentifier();
|
||||
}
|
||||
|
||||
/**
|
||||
* 运行时支撑的 AgentScope 会话键。
|
||||
*/
|
||||
private static class RuntimeSessionKey implements SessionKey {
|
||||
|
||||
private final String value;
|
||||
|
||||
private RuntimeSessionKey(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将键转换为稳定标识符。
|
||||
*
|
||||
* @return 标识符
|
||||
*/
|
||||
@Override
|
||||
public String toIdentifier() {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
package com.easyagents.agent.runtime.agentscope;
|
||||
|
||||
import com.easyagents.agent.runtime.skill.AgentSkillBoxSpec;
|
||||
import com.easyagents.agent.runtime.skill.AgentSkillCompiler;
|
||||
import com.easyagents.agent.runtime.skill.AgentSkillSpec;
|
||||
import io.agentscope.core.skill.AgentSkill;
|
||||
import io.agentscope.core.skill.SkillBox;
|
||||
import io.agentscope.core.tool.AgentTool;
|
||||
import io.agentscope.core.tool.Toolkit;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 将运行时 Skill 适配为 AgentScope SkillBox。
|
||||
*/
|
||||
public class AgentScopeSkillAdapter implements AgentSkillCompiler<AgentSkill> {
|
||||
|
||||
@Override
|
||||
public AgentSkill compile(AgentSkillSpec skillSpec) {
|
||||
Map<String, Object> metadata = new LinkedHashMap<>(skillSpec.getMetadata());
|
||||
metadata.put("skillId", skillSpec.getSkillId());
|
||||
metadata.put("name", skillSpec.getName());
|
||||
metadata.put("description", skillSpec.getDescription());
|
||||
return AgentSkill.builder()
|
||||
.name(skillSpec.getName())
|
||||
.description(skillSpec.getDescription())
|
||||
.skillContent(skillSpec.getSkillContent())
|
||||
.metadata(metadata)
|
||||
.resources(skillSpec.getResources())
|
||||
.source(skillSpec.getSource())
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建并绑定 AgentScope SkillBox。
|
||||
*
|
||||
* @param spec SkillBox 声明
|
||||
* @param toolkit Toolkit 实例
|
||||
* @return SkillBox;未配置 Skill 时返回 null
|
||||
*/
|
||||
public SkillBox createSkillBox(AgentSkillBoxSpec spec, Toolkit toolkit) {
|
||||
if (spec == null || spec.getSkills().isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
SkillBox skillBox = spec.getSkillBoxId() == null || spec.getSkillBoxId().isEmpty()
|
||||
? new SkillBox(toolkit)
|
||||
: new SkillBox(toolkit, spec.getSkillBoxId());
|
||||
skillBox.setExposeAllSkillMetadata(spec.isExposeAllSkillMetadata());
|
||||
for (AgentSkillSpec skillSpec : spec.getSkills()) {
|
||||
AgentSkill skill = compile(skillSpec);
|
||||
SkillBox.SkillRegistration registration = skillBox.registration()
|
||||
.skill(skill)
|
||||
.toolkit(toolkit)
|
||||
.enableTools(spec.getEnabledToolNames())
|
||||
.disableTools(spec.getDisabledToolNames())
|
||||
.presetParameters(spec.getPresetParameters());
|
||||
registration.apply();
|
||||
}
|
||||
skillBox.syncToolGroupStates();
|
||||
return skillBox;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建并绑定带 Skill 工具的 AgentScope SkillBox。
|
||||
*
|
||||
* @param spec SkillBox 声明
|
||||
* @param toolkit Toolkit 实例
|
||||
* @param skillTools 按 Skill ID 分组的工具
|
||||
* @return SkillBox;未配置 Skill 时返回 null
|
||||
*/
|
||||
public SkillBox createSkillBox(AgentSkillBoxSpec spec, Toolkit toolkit, Map<String, List<AgentTool>> skillTools) {
|
||||
if (spec == null || spec.getSkills().isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
SkillBox skillBox = spec.getSkillBoxId() == null || spec.getSkillBoxId().isEmpty()
|
||||
? new SkillBox(toolkit)
|
||||
: new SkillBox(toolkit, spec.getSkillBoxId());
|
||||
skillBox.setExposeAllSkillMetadata(spec.isExposeAllSkillMetadata());
|
||||
for (AgentSkillSpec skillSpec : spec.getSkills()) {
|
||||
AgentSkill skill = compile(skillSpec);
|
||||
List<AgentTool> tools = skillTools == null ? List.of() : skillTools.getOrDefault(skillSpec.getSkillId(), List.of());
|
||||
if (tools.isEmpty()) {
|
||||
skillBox.registration()
|
||||
.skill(skill)
|
||||
.toolkit(toolkit)
|
||||
.enableTools(spec.getEnabledToolNames())
|
||||
.disableTools(spec.getDisabledToolNames())
|
||||
.presetParameters(spec.getPresetParameters())
|
||||
.apply();
|
||||
continue;
|
||||
}
|
||||
for (AgentTool tool : tools) {
|
||||
skillBox.registration()
|
||||
.skill(skill)
|
||||
.toolkit(toolkit)
|
||||
.enableTools(spec.getEnabledToolNames())
|
||||
.disableTools(spec.getDisabledToolNames())
|
||||
.presetParameters(spec.getPresetParameters())
|
||||
.agentTool(tool)
|
||||
.apply();
|
||||
}
|
||||
}
|
||||
skillBox.syncToolGroupStates();
|
||||
return skillBox;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,459 @@
|
||||
package com.easyagents.agent.runtime.agentscope;
|
||||
|
||||
import com.easyagents.agent.runtime.AgentRunRequest;
|
||||
import com.easyagents.agent.runtime.AgentRuntimeException;
|
||||
import com.easyagents.agent.runtime.event.AgentRuntimeEvent;
|
||||
import com.easyagents.agent.runtime.event.AgentRuntimeEventType;
|
||||
import com.easyagents.agent.runtime.hitl.AgentPendingState;
|
||||
import com.easyagents.agent.runtime.hitl.AgentToolApprovalCoordinator;
|
||||
import com.easyagents.agent.runtime.hitl.AgentToolApprovalRejectedException;
|
||||
import com.easyagents.agent.runtime.skill.AgentSkillBinding;
|
||||
import com.easyagents.agent.runtime.skill.AgentSkillRuntimeContext;
|
||||
import com.easyagents.agent.runtime.tool.AgentToolContext;
|
||||
import com.easyagents.agent.runtime.tool.AgentToolInvoker;
|
||||
import com.easyagents.agent.runtime.tool.AgentToolResult;
|
||||
import com.easyagents.agent.runtime.tool.AgentToolSpec;
|
||||
import io.agentscope.core.message.TextBlock;
|
||||
import io.agentscope.core.message.ToolResultBlock;
|
||||
import io.agentscope.core.message.ToolUseBlock;
|
||||
import io.agentscope.core.tool.AgentTool;
|
||||
import io.agentscope.core.tool.ToolCallParam;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.core.publisher.Sinks;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* 将运行时工具适配为 AgentScope 工具。
|
||||
*/
|
||||
public class AgentScopeToolAdapter {
|
||||
|
||||
/**
|
||||
* 将运行时工具声明和调用器转换为 AgentScope AgentTool。
|
||||
*
|
||||
* @param toolSpec 工具声明
|
||||
* @param invoker 工具调用器
|
||||
* @param request 运行请求
|
||||
* @return AgentScope 工具
|
||||
*/
|
||||
public AgentTool adapt(AgentToolSpec toolSpec, AgentToolInvoker invoker, AgentRunRequest request) {
|
||||
return adapt(toolSpec, invoker, request, AgentToolApprovalCoordinator.disabled(), null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将运行时工具声明和调用器转换为带事件 sink 的 AgentScope AgentTool。
|
||||
*
|
||||
* @param toolSpec 工具声明
|
||||
* @param invoker 工具调用器
|
||||
* @param request 运行请求
|
||||
* @param eventSink 事件 sink
|
||||
* @return AgentScope 工具
|
||||
*/
|
||||
public AgentTool adapt(AgentToolSpec toolSpec,
|
||||
AgentToolInvoker invoker,
|
||||
AgentRunRequest request,
|
||||
Sinks.Many<AgentRuntimeEvent> eventSink) {
|
||||
return adapt(toolSpec, invoker, request, AgentToolApprovalCoordinator.disabled(), eventSink);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将运行时工具声明和调用器转换为带审批协调器和事件 sink 的 AgentScope AgentTool。
|
||||
*
|
||||
* @param toolSpec 工具声明
|
||||
* @param invoker 工具调用器
|
||||
* @param request 运行请求
|
||||
* @param approvalCoordinator 审批协调器
|
||||
* @param eventSink 事件 sink
|
||||
* @return AgentScope 工具
|
||||
*/
|
||||
public AgentTool adapt(AgentToolSpec toolSpec,
|
||||
AgentToolInvoker invoker,
|
||||
AgentRunRequest request,
|
||||
AgentToolApprovalCoordinator approvalCoordinator,
|
||||
Sinks.Many<AgentRuntimeEvent> eventSink) {
|
||||
return adapt(toolSpec, invoker, request, approvalCoordinator, eventSink, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将运行时工具声明和调用器转换为带 Skill 归属的 AgentScope AgentTool。
|
||||
*
|
||||
* @param toolSpec 工具声明
|
||||
* @param invoker 工具调用器
|
||||
* @param request 运行请求
|
||||
* @param approvalCoordinator 审批协调器
|
||||
* @param eventSink 事件 sink
|
||||
* @param skillBinding Skill 绑定关系
|
||||
* @return AgentScope 工具
|
||||
*/
|
||||
public AgentTool adapt(AgentToolSpec toolSpec,
|
||||
AgentToolInvoker invoker,
|
||||
AgentRunRequest request,
|
||||
AgentToolApprovalCoordinator approvalCoordinator,
|
||||
Sinks.Many<AgentRuntimeEvent> eventSink,
|
||||
AgentSkillBinding skillBinding) {
|
||||
return adapt(toolSpec, invoker, request, approvalCoordinator, eventSink, null, skillBinding);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将运行时工具声明和调用器转换为带 Skill 上下文的 AgentScope AgentTool。
|
||||
*
|
||||
* @param toolSpec 工具声明
|
||||
* @param invoker 工具调用器
|
||||
* @param request 运行请求
|
||||
* @param approvalCoordinator 审批协调器
|
||||
* @param eventSink 事件 sink
|
||||
* @param skillContext Skill 运行时上下文
|
||||
* @param skillBinding Skill 静态绑定关系
|
||||
* @return AgentScope 工具
|
||||
*/
|
||||
public AgentTool adapt(AgentToolSpec toolSpec,
|
||||
AgentToolInvoker invoker,
|
||||
AgentRunRequest request,
|
||||
AgentToolApprovalCoordinator approvalCoordinator,
|
||||
Sinks.Many<AgentRuntimeEvent> eventSink,
|
||||
AgentSkillRuntimeContext skillContext,
|
||||
AgentSkillBinding skillBinding) {
|
||||
if (toolSpec == null || toolSpec.getName() == null) {
|
||||
throw new AgentRuntimeException("Agent tool spec and name are required.");
|
||||
}
|
||||
if (invoker == null) {
|
||||
throw new AgentRuntimeException("Agent tool invoker is required: " + toolSpec.getName());
|
||||
}
|
||||
return new RuntimeAgentTool(toolSpec, invoker, request, approvalCoordinator, eventSink, skillContext, skillBinding);
|
||||
}
|
||||
|
||||
private record RuntimeAgentTool(AgentToolSpec toolSpec,
|
||||
AgentToolInvoker invoker,
|
||||
AgentRunRequest request,
|
||||
AgentToolApprovalCoordinator approvalCoordinator,
|
||||
Sinks.Many<AgentRuntimeEvent> eventSink,
|
||||
AgentSkillRuntimeContext skillContext,
|
||||
AgentSkillBinding skillBinding) implements AgentTool {
|
||||
|
||||
/**
|
||||
* 获取工具名称。
|
||||
*
|
||||
* @return 工具名称
|
||||
*/
|
||||
@Override
|
||||
public String getName() {
|
||||
return toolSpec.getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取工具描述。
|
||||
*
|
||||
* @return 工具描述
|
||||
*/
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return toolSpec.getDescription();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取工具参数 Schema。
|
||||
*
|
||||
* @return 参数 Schema
|
||||
*/
|
||||
@Override
|
||||
public Map<String, Object> getParameters() {
|
||||
return toolSpec.getParametersSchema();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取输出 Schema。
|
||||
*
|
||||
* @return 输出 Schema
|
||||
*/
|
||||
@Override
|
||||
public Map<String, Object> getOutputSchema() {
|
||||
return toolSpec.getOutputSchema();
|
||||
}
|
||||
|
||||
/**
|
||||
* 调用运行时工具。
|
||||
*
|
||||
* @param param 工具调用参数
|
||||
* @return 工具结果块
|
||||
*/
|
||||
@Override
|
||||
public Mono<ToolResultBlock> callAsync(ToolCallParam param) {
|
||||
emit(toolCallEvent(param == null ? null : param.getToolUseBlock()));
|
||||
Map<String, Object> input = param == null || param.getInput() == null
|
||||
? new LinkedHashMap<>()
|
||||
: new LinkedHashMap<>(param.getInput());
|
||||
if (toolSpec.isApprovalRequired()) {
|
||||
if (approvalCoordinator == null) {
|
||||
throw new AgentRuntimeException("Agent tool approval coordinator is required: " + toolSpec.getName());
|
||||
}
|
||||
Instant expiresAt = Instant.now().plus(
|
||||
toolSpec.getApprovalRequest() != null && toolSpec.getApprovalRequest().getTimeout() != null
|
||||
? toolSpec.getApprovalRequest().getTimeout()
|
||||
: java.time.Duration.ofMinutes(30));
|
||||
AgentPendingState pendingState = approvalCoordinator.register(
|
||||
request.getSessionId(),
|
||||
request.getAgentDefinition().getAgentId(),
|
||||
param != null && param.getToolUseBlock() != null ? param.getToolUseBlock().getId() : null,
|
||||
toolSpec.getName(),
|
||||
approvalPrompt(toolSpec.getApprovalRequest() == null ? null : toolSpec.getApprovalRequest().getApprovalPrompt()),
|
||||
input,
|
||||
toolSpec.getApprovalRequest() == null ? new LinkedHashMap<>() : toolSpec.getApprovalRequest().getMetadata(),
|
||||
expiresAt);
|
||||
AgentRuntimeEvent approvalEvent = toolApprovalRequiredEvent(param == null ? null : param.getToolUseBlock(), pendingState);
|
||||
pendingState.setEventId(approvalEvent.getEventId());
|
||||
emit(approvalEvent);
|
||||
return approvalCoordinator.await(pendingState.getResumeToken())
|
||||
.flatMap(response -> {
|
||||
if (!response.isApproved()) {
|
||||
return Mono.error(new AgentToolApprovalRejectedException(
|
||||
response.getRejectReason()));
|
||||
}
|
||||
return Mono.fromCallable(() -> invokeTool(param, input));
|
||||
});
|
||||
}
|
||||
return Mono.fromCallable(() -> invokeTool(param, input));
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建中立工具上下文。
|
||||
*
|
||||
* @param param 工具调用参数
|
||||
* @return 工具上下文
|
||||
*/
|
||||
private AgentToolContext buildContext(ToolCallParam param) {
|
||||
AgentToolContext context = new AgentToolContext();
|
||||
context.setRequestId(request.getRequestId());
|
||||
context.setTraceId(request.getTraceId());
|
||||
context.setSessionId(request.getSessionId());
|
||||
context.setAgentId(request.getAgentDefinition().getAgentId());
|
||||
context.setRuntimeContext(request.getRuntimeContext());
|
||||
if (param != null && param.getToolUseBlock() != null) {
|
||||
context.setToolCallId(param.getToolUseBlock().getId());
|
||||
}
|
||||
context.getMetadata().put("toolName", toolSpec.getName());
|
||||
context.getMetadata().put("category", toolSpec.getCategory());
|
||||
appendSkillPayload(context.getMetadata(), activeSkillBinding());
|
||||
context.getMetadata().putAll(toolSpec.getMetadata());
|
||||
return context;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将运行时结果转换为 AgentScope 结果块。
|
||||
*
|
||||
* @param param 工具调用参数
|
||||
* @param result 运行时结果
|
||||
* @return AgentScope 结果块
|
||||
*/
|
||||
private ToolResultBlock toToolResultBlock(ToolCallParam param, AgentToolResult result) {
|
||||
AgentToolResult safeResult = result == null ? AgentToolResult.failure("Tool returned null result.") : result;
|
||||
Map<String, Object> metadata = new LinkedHashMap<>(safeResult.getMetadata());
|
||||
metadata.put("success", safeResult.isSuccess());
|
||||
metadata.put("visibility", toolSpec.getVisibility().name());
|
||||
metadata.put("displayContent", safeResult.getDisplayContent());
|
||||
appendSkillPayload(metadata, activeSkillBinding());
|
||||
String output = safeResult.getModelContent();
|
||||
if (output == null || output.isEmpty()) {
|
||||
output = safeResult.isSuccess() ? "" : safeResult.getErrorMessage();
|
||||
}
|
||||
String id = param != null && param.getToolUseBlock() != null ? param.getToolUseBlock().getId() : null;
|
||||
return ToolResultBlock.of(id, toolSpec.getName(), TextBlock.builder().text(output).build(), metadata);
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行真实工具。
|
||||
*
|
||||
* @param param 工具调用参数
|
||||
* @param input 工具输入
|
||||
* @return 工具结果块
|
||||
*/
|
||||
private ToolResultBlock invokeTool(ToolCallParam param, Map<String, Object> input) {
|
||||
AgentToolContext context = buildContext(param);
|
||||
AgentToolResult result = invoker.invoke(input, context);
|
||||
ToolResultBlock block = toToolResultBlock(param, result);
|
||||
emit(toolResultEvent(block));
|
||||
return block;
|
||||
}
|
||||
|
||||
/**
|
||||
* 存在 sink 时发射一条事件。
|
||||
*
|
||||
* @param event 事件
|
||||
*/
|
||||
private void emit(AgentRuntimeEvent event) {
|
||||
if (eventSink != null && event != null) {
|
||||
Sinks.EmitResult result = eventSink.tryEmitNext(event);
|
||||
if (result.isFailure()) {
|
||||
throw new AgentRuntimeException("Failed to emit agent runtime event: " + result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建工具调用事件。
|
||||
*
|
||||
* @param block 工具使用块
|
||||
* @return 运行时事件
|
||||
*/
|
||||
private AgentRuntimeEvent toolCallEvent(ToolUseBlock block) {
|
||||
AgentSkillBinding activeBinding = activeSkillBinding();
|
||||
AgentRuntimeEvent event = baseEvent(activeBinding == null
|
||||
? AgentRuntimeEventType.TOOL_CALL
|
||||
: AgentRuntimeEventType.SKILL_STEP);
|
||||
if (block != null) {
|
||||
event.setToolCallId(block.getId());
|
||||
event.getPayload().put("name", block.getName());
|
||||
event.getPayload().put("toolName", block.getName());
|
||||
event.getPayload().put("input", block.getInput());
|
||||
event.getPayload().put("content", block.getContent());
|
||||
event.getMetadata().putAll(block.getMetadata() == null ? new LinkedHashMap<>() : block.getMetadata());
|
||||
} else {
|
||||
event.getPayload().put("name", toolSpec.getName());
|
||||
event.getPayload().put("toolName", toolSpec.getName());
|
||||
}
|
||||
if (activeBinding != null) {
|
||||
appendSkillPayload(event.getPayload(), activeBinding);
|
||||
event.getPayload().put("stepType", "TOOL_CALL");
|
||||
event.getPayload().put("stepName", toolSpec.getName());
|
||||
event.getPayload().put("status", "RUNNING");
|
||||
}
|
||||
return event;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建工具结果事件。
|
||||
*
|
||||
* @param block 工具结果块
|
||||
* @return 运行时事件
|
||||
*/
|
||||
private AgentRuntimeEvent toolResultEvent(ToolResultBlock block) {
|
||||
AgentSkillBinding activeBinding = activeSkillBinding();
|
||||
AgentRuntimeEvent event = baseEvent(activeBinding == null
|
||||
? AgentRuntimeEventType.TOOL_RESULT
|
||||
: AgentRuntimeEventType.SKILL_STEP);
|
||||
if (block != null) {
|
||||
event.setToolCallId(block.getId());
|
||||
event.getPayload().put("name", block.getName());
|
||||
event.getPayload().put("toolName", block.getName());
|
||||
event.getPayload().put("text", block.toString());
|
||||
event.getPayload().put("suspended", block.isSuspended());
|
||||
event.getMetadata().putAll(block.getMetadata() == null ? new LinkedHashMap<>() : block.getMetadata());
|
||||
}
|
||||
if (activeBinding != null) {
|
||||
appendSkillPayload(event.getPayload(), activeBinding);
|
||||
event.getPayload().put("stepType", "TOOL_RESULT");
|
||||
event.getPayload().put("stepName", toolSpec.getName());
|
||||
event.getPayload().put("status", success(event) ? "SUCCESS" : "FAILED");
|
||||
event.getPayload().put("success", success(event));
|
||||
}
|
||||
return event;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据挂起的工具结果构建工具审批事件。
|
||||
*
|
||||
* @param toolUseBlock 工具使用块
|
||||
* @param resultBlock 工具结果块
|
||||
* @return 工具审批事件
|
||||
*/
|
||||
private AgentRuntimeEvent toolApprovalRequiredEvent(ToolUseBlock toolUseBlock, AgentPendingState pendingState) {
|
||||
AgentRuntimeEvent event = baseEvent(AgentRuntimeEventType.TOOL_APPROVAL_REQUIRED);
|
||||
if (pendingState != null && pendingState.getToolCallId() != null) {
|
||||
event.setToolCallId(pendingState.getToolCallId());
|
||||
}
|
||||
Map<String, Object> payload = new LinkedHashMap<>();
|
||||
payload.put("resumeToken", pendingState == null || pendingState.getResumeToken() == null
|
||||
? UUID.randomUUID().toString()
|
||||
: pendingState.getResumeToken().getValue());
|
||||
payload.put("sessionId", request.getSessionId());
|
||||
payload.put("agentId", request.getAgentDefinition().getAgentId());
|
||||
payload.put("approvalPrompt", approvalPrompt(pendingState == null ? null : pendingState.getApprovalPrompt()));
|
||||
payload.put("approvalMetadata", pendingState == null ? new LinkedHashMap<>() : pendingState.getMetadata());
|
||||
payload.put("toolInput", pendingState == null ? new LinkedHashMap<>() : pendingState.getToolInput());
|
||||
payload.put("toolName", pendingState == null ? toolSpec.getName() : pendingState.getToolName());
|
||||
payload.put("expiresAt", pendingState == null || pendingState.getExpiresAt() == null ? null : pendingState.getExpiresAt().toString());
|
||||
appendSkillPayload(payload, activeSkillBinding());
|
||||
if (toolUseBlock != null) {
|
||||
payload.put("toolCallId", toolUseBlock.getId());
|
||||
payload.put("toolName", toolUseBlock.getName());
|
||||
payload.put("input", toolUseBlock.getInput());
|
||||
}
|
||||
event.setPayload(payload);
|
||||
return event;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析审批文案。
|
||||
*
|
||||
* @param approvalPrompt 审批文案
|
||||
* @return 审批文案
|
||||
*/
|
||||
private String approvalPrompt(String approvalPrompt) {
|
||||
if (approvalPrompt != null && !approvalPrompt.isBlank()) {
|
||||
return approvalPrompt;
|
||||
}
|
||||
return "是否批准执行该工具?";
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建带请求身份信息的事件。
|
||||
*
|
||||
* @param type 事件类型
|
||||
* @return 运行时事件
|
||||
*/
|
||||
private AgentRuntimeEvent baseEvent(AgentRuntimeEventType type) {
|
||||
AgentRuntimeEvent event = AgentRuntimeEvent.of(type);
|
||||
event.setTraceId(request.getTraceId());
|
||||
event.setSessionId(request.getSessionId());
|
||||
event.setAgentId(request.getAgentDefinition().getAgentId());
|
||||
event.getMetadata().put("requestId", request.getRequestId());
|
||||
event.getMetadata().put("toolCategory", toolSpec.getCategory().name());
|
||||
event.getMetadata().put("visibility", toolSpec.getVisibility().name());
|
||||
appendSkillPayload(event.getMetadata(), activeSkillBinding());
|
||||
return event;
|
||||
}
|
||||
|
||||
/**
|
||||
* 追加 Skill 归属字段。
|
||||
*
|
||||
* @param target 目标 Map
|
||||
* @param binding Skill 绑定关系
|
||||
*/
|
||||
private void appendSkillPayload(Map<String, Object> target, AgentSkillBinding binding) {
|
||||
if (binding == null || target == null) {
|
||||
return;
|
||||
}
|
||||
target.put("skillId", binding.getSkillId());
|
||||
target.put("skillName", binding.getSkillName());
|
||||
target.put("skillBoxId", binding.getSkillBoxId());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前已激活的 Skill 绑定关系。
|
||||
*
|
||||
* @return 已激活绑定关系
|
||||
*/
|
||||
private AgentSkillBinding activeSkillBinding() {
|
||||
if (skillBinding == null) {
|
||||
return null;
|
||||
}
|
||||
if (skillContext == null) {
|
||||
return skillBinding;
|
||||
}
|
||||
return skillContext.getActiveToolBinding(toolSpec.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* 从事件元数据判断工具结果是否成功。
|
||||
*
|
||||
* @param event 运行时事件
|
||||
* @return 成功时为 true
|
||||
*/
|
||||
private boolean success(AgentRuntimeEvent event) {
|
||||
Object success = event.getMetadata().get("success");
|
||||
return !(success instanceof Boolean) || Boolean.TRUE.equals(success);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,236 @@
|
||||
package com.easyagents.agent.runtime.event;
|
||||
|
||||
import com.easyagents.agent.runtime.message.AgentMessage;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* 智能体执行过程中的中立运行时事件。
|
||||
*/
|
||||
public class AgentRuntimeEvent {
|
||||
|
||||
private String eventId = UUID.randomUUID().toString();
|
||||
private AgentRuntimeEventType eventType;
|
||||
private String traceId;
|
||||
private String sessionId;
|
||||
private String agentId;
|
||||
private String messageId;
|
||||
private String toolCallId;
|
||||
private AgentMessage message;
|
||||
private Map<String, Object> payload = new LinkedHashMap<>();
|
||||
private Map<String, Object> metadata = new LinkedHashMap<>();
|
||||
private Instant createdAt = Instant.now();
|
||||
|
||||
/**
|
||||
* 创建指定类型的事件。
|
||||
*
|
||||
* @param eventType 事件类型
|
||||
* @return 新事件
|
||||
*/
|
||||
public static AgentRuntimeEvent of(AgentRuntimeEventType eventType) {
|
||||
AgentRuntimeEvent event = new AgentRuntimeEvent();
|
||||
event.setEventType(eventType);
|
||||
return event;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取事件ID。
|
||||
*
|
||||
* @return 事件ID
|
||||
*/
|
||||
public String getEventId() {
|
||||
return eventId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置事件ID。
|
||||
*
|
||||
* @param eventId 事件ID
|
||||
*/
|
||||
public void setEventId(String eventId) {
|
||||
this.eventId = eventId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取事件类型。
|
||||
*
|
||||
* @return 事件类型
|
||||
*/
|
||||
public AgentRuntimeEventType getEventType() {
|
||||
return eventType;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置事件类型。
|
||||
*
|
||||
* @param eventType 事件类型
|
||||
*/
|
||||
public void setEventType(AgentRuntimeEventType eventType) {
|
||||
this.eventType = eventType;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取链路ID。
|
||||
*
|
||||
* @return 链路ID
|
||||
*/
|
||||
public String getTraceId() {
|
||||
return traceId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置链路ID。
|
||||
*
|
||||
* @param traceId 链路ID
|
||||
*/
|
||||
public void setTraceId(String traceId) {
|
||||
this.traceId = traceId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取会话ID。
|
||||
*
|
||||
* @return 会话ID
|
||||
*/
|
||||
public String getSessionId() {
|
||||
return sessionId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置会话ID。
|
||||
*
|
||||
* @param sessionId 会话ID
|
||||
*/
|
||||
public void setSessionId(String sessionId) {
|
||||
this.sessionId = sessionId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取智能体ID。
|
||||
*
|
||||
* @return 智能体ID
|
||||
*/
|
||||
public String getAgentId() {
|
||||
return agentId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置智能体ID。
|
||||
*
|
||||
* @param agentId 智能体ID
|
||||
*/
|
||||
public void setAgentId(String agentId) {
|
||||
this.agentId = agentId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取消息ID。
|
||||
*
|
||||
* @return 消息ID
|
||||
*/
|
||||
public String getMessageId() {
|
||||
return messageId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置消息ID。
|
||||
*
|
||||
* @param messageId 消息ID
|
||||
*/
|
||||
public void setMessageId(String messageId) {
|
||||
this.messageId = messageId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取工具调用ID。
|
||||
*
|
||||
* @return 工具调用ID
|
||||
*/
|
||||
public String getToolCallId() {
|
||||
return toolCallId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置工具调用ID。
|
||||
*
|
||||
* @param toolCallId 工具调用ID
|
||||
*/
|
||||
public void setToolCallId(String toolCallId) {
|
||||
this.toolCallId = toolCallId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取结构化消息。
|
||||
*
|
||||
* @return 结构化消息
|
||||
*/
|
||||
public AgentMessage getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置结构化消息。
|
||||
*
|
||||
* @param message 结构化消息
|
||||
*/
|
||||
public void setMessage(AgentMessage message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取载荷。
|
||||
*
|
||||
* @return 载荷
|
||||
*/
|
||||
public Map<String, Object> getPayload() {
|
||||
return payload;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置载荷。
|
||||
*
|
||||
* @param payload 载荷
|
||||
*/
|
||||
public void setPayload(Map<String, Object> payload) {
|
||||
this.payload = payload == null ? new LinkedHashMap<>() : payload;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取元数据。
|
||||
*
|
||||
* @return 元数据
|
||||
*/
|
||||
public Map<String, Object> getMetadata() {
|
||||
return metadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置元数据。
|
||||
*
|
||||
* @param metadata 元数据
|
||||
*/
|
||||
public void setMetadata(Map<String, Object> metadata) {
|
||||
this.metadata = metadata == null ? new LinkedHashMap<>() : metadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取创建时间。
|
||||
*
|
||||
* @return 创建时间
|
||||
*/
|
||||
public Instant getCreatedAt() {
|
||||
return createdAt;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置创建时间。
|
||||
*
|
||||
* @param createdAt 创建时间
|
||||
*/
|
||||
public void setCreatedAt(Instant createdAt) {
|
||||
this.createdAt = createdAt == null ? Instant.now() : createdAt;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package com.easyagents.agent.runtime.event;
|
||||
|
||||
/**
|
||||
* 智能体运行输出的运行时事件类型。
|
||||
*/
|
||||
public enum AgentRuntimeEventType {
|
||||
/**
|
||||
* 智能体运行已开始。
|
||||
*/
|
||||
STARTED,
|
||||
|
||||
/**
|
||||
* 流式推理内容,用于展示思考过程。
|
||||
*/
|
||||
REASONING_DELTA,
|
||||
|
||||
/**
|
||||
* 流式输出内容,用于流式展示聊天内容。
|
||||
*/
|
||||
MESSAGE_DELTA,
|
||||
|
||||
/**
|
||||
* 智能体发起工具调用。
|
||||
*/
|
||||
TOOL_CALL,
|
||||
|
||||
/**
|
||||
* 工具调用完成并返回结果。
|
||||
*/
|
||||
TOOL_RESULT,
|
||||
|
||||
/**
|
||||
* 知识库检索完成并返回文档摘要。
|
||||
*/
|
||||
KNOWLEDGE_RETRIEVAL,
|
||||
|
||||
/**
|
||||
* 工具执行前需要人工审批。
|
||||
*/
|
||||
TOOL_APPROVAL_REQUIRED,
|
||||
|
||||
/**
|
||||
* 智能体开始加载并调用 Skill。
|
||||
*/
|
||||
SKILL_CALL,
|
||||
|
||||
/**
|
||||
* Skill 内部执行步骤。
|
||||
*/
|
||||
SKILL_STEP,
|
||||
|
||||
/**
|
||||
* Skill 加载或调用完成。
|
||||
*/
|
||||
SKILL_RESULT,
|
||||
|
||||
/**
|
||||
* Skill 加载或调用失败。
|
||||
*/
|
||||
SKILL_FAILED,
|
||||
|
||||
/**
|
||||
* 智能体处理完成。
|
||||
*/
|
||||
COMPLETED,
|
||||
|
||||
/**
|
||||
* 智能体运行失败。
|
||||
*/
|
||||
FAILED,
|
||||
|
||||
/**
|
||||
* 智能体运行被取消。
|
||||
*/
|
||||
CANCELLED
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1,202 @@
|
||||
package com.easyagents.agent.runtime.hitl;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 等待工具执行审批的状态。
|
||||
*/
|
||||
public class AgentPendingState {
|
||||
|
||||
private AgentResumeToken resumeToken = AgentResumeToken.create();
|
||||
private String sessionId;
|
||||
private String agentId;
|
||||
private String eventId;
|
||||
private String toolCallId;
|
||||
private String toolName;
|
||||
private String approvalPrompt;
|
||||
private Map<String, Object> toolInput = new LinkedHashMap<>();
|
||||
private Instant expiresAt;
|
||||
private Map<String, Object> metadata = new LinkedHashMap<>();
|
||||
|
||||
/**
|
||||
* 获取恢复令牌。
|
||||
*
|
||||
* @return 恢复令牌
|
||||
*/
|
||||
public AgentResumeToken getResumeToken() {
|
||||
return resumeToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置恢复令牌。
|
||||
*
|
||||
* @param resumeToken 恢复令牌
|
||||
*/
|
||||
public void setResumeToken(AgentResumeToken resumeToken) {
|
||||
this.resumeToken = resumeToken == null ? AgentResumeToken.create() : resumeToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取会话ID。
|
||||
*
|
||||
* @return 会话ID
|
||||
*/
|
||||
public String getSessionId() {
|
||||
return sessionId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置会话ID。
|
||||
*
|
||||
* @param sessionId 会话ID
|
||||
*/
|
||||
public void setSessionId(String sessionId) {
|
||||
this.sessionId = sessionId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取智能体ID。
|
||||
*
|
||||
* @return 智能体ID
|
||||
*/
|
||||
public String getAgentId() {
|
||||
return agentId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置智能体ID。
|
||||
*
|
||||
* @param agentId 智能体ID
|
||||
*/
|
||||
public void setAgentId(String agentId) {
|
||||
this.agentId = agentId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取事件ID。
|
||||
*
|
||||
* @return 事件ID
|
||||
*/
|
||||
public String getEventId() {
|
||||
return eventId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置事件ID。
|
||||
*
|
||||
* @param eventId 事件ID
|
||||
*/
|
||||
public void setEventId(String eventId) {
|
||||
this.eventId = eventId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取工具调用ID。
|
||||
*
|
||||
* @return 工具调用ID
|
||||
*/
|
||||
public String getToolCallId() {
|
||||
return toolCallId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置工具调用ID。
|
||||
*
|
||||
* @param toolCallId 工具调用ID
|
||||
*/
|
||||
public void setToolCallId(String toolCallId) {
|
||||
this.toolCallId = toolCallId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取工具名称。
|
||||
*
|
||||
* @return 工具名称
|
||||
*/
|
||||
public String getToolName() {
|
||||
return toolName;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置工具名称。
|
||||
*
|
||||
* @param toolName 工具名称
|
||||
*/
|
||||
public void setToolName(String toolName) {
|
||||
this.toolName = toolName;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取审批提示文案。
|
||||
*
|
||||
* @return 审批提示文案
|
||||
*/
|
||||
public String getApprovalPrompt() {
|
||||
return approvalPrompt;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置审批提示文案。
|
||||
*
|
||||
* @param approvalPrompt 审批提示文案
|
||||
*/
|
||||
public void setApprovalPrompt(String approvalPrompt) {
|
||||
this.approvalPrompt = approvalPrompt;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取工具调用参数。
|
||||
*
|
||||
* @return 工具调用参数
|
||||
*/
|
||||
public Map<String, Object> getToolInput() {
|
||||
return toolInput;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置工具调用参数。
|
||||
*
|
||||
* @param toolInput 工具调用参数
|
||||
*/
|
||||
public void setToolInput(Map<String, Object> toolInput) {
|
||||
this.toolInput = toolInput == null ? new LinkedHashMap<>() : toolInput;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取过期时间。
|
||||
*
|
||||
* @return 过期时间
|
||||
*/
|
||||
public Instant getExpiresAt() {
|
||||
return expiresAt;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置过期时间。
|
||||
*
|
||||
* @param expiresAt 过期时间
|
||||
*/
|
||||
public void setExpiresAt(Instant expiresAt) {
|
||||
this.expiresAt = expiresAt;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取元数据。
|
||||
*
|
||||
* @return 元数据
|
||||
*/
|
||||
public Map<String, Object> getMetadata() {
|
||||
return metadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置元数据。
|
||||
*
|
||||
* @param metadata 元数据
|
||||
*/
|
||||
public void setMetadata(Map<String, Object> metadata) {
|
||||
this.metadata = metadata == null ? new LinkedHashMap<>() : metadata;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package com.easyagents.agent.runtime.hitl;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* 用于恢复挂起运行的令牌。
|
||||
*/
|
||||
public class AgentResumeToken {
|
||||
|
||||
private String value = UUID.randomUUID().toString();
|
||||
|
||||
/**
|
||||
* 创建a new token。
|
||||
*
|
||||
* @return 令牌
|
||||
*/
|
||||
public static AgentResumeToken create() {
|
||||
return new AgentResumeToken();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取令牌值。
|
||||
*
|
||||
* @return 令牌值
|
||||
*/
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置令牌值。
|
||||
*
|
||||
* @param value 令牌值
|
||||
*/
|
||||
public void setValue(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.easyagents.agent.runtime.hitl;
|
||||
|
||||
/**
|
||||
* 已挂起的运行时状态。
|
||||
*/
|
||||
public class AgentSuspendState {
|
||||
|
||||
private AgentPendingState pendingState;
|
||||
|
||||
/**
|
||||
* 获取挂起状态。
|
||||
*
|
||||
* @return 挂起状态
|
||||
*/
|
||||
public AgentPendingState getPendingState() {
|
||||
return pendingState;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置挂起状态。
|
||||
*
|
||||
* @param pendingState 挂起状态
|
||||
*/
|
||||
public void setPendingState(AgentPendingState pendingState) {
|
||||
this.pendingState = pendingState;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
package com.easyagents.agent.runtime.hitl;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* 工具审批协调器。
|
||||
*/
|
||||
public class AgentToolApprovalCoordinator {
|
||||
|
||||
private final boolean enabled;
|
||||
private final Map<String, PendingApproval> approvals = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* 创建已启用的协调器。
|
||||
*
|
||||
* @return 已启用的协调器
|
||||
*/
|
||||
public static AgentToolApprovalCoordinator enabled() {
|
||||
return new AgentToolApprovalCoordinator(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建禁用的协调器。
|
||||
*
|
||||
* @return 禁用的协调器
|
||||
*/
|
||||
public static AgentToolApprovalCoordinator disabled() {
|
||||
return new AgentToolApprovalCoordinator(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建协调器。
|
||||
*
|
||||
* @param enabled 是否启用
|
||||
*/
|
||||
public AgentToolApprovalCoordinator(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册一个待审批请求。
|
||||
*
|
||||
* @param sessionId 会话ID
|
||||
* @param agentId 智能体ID
|
||||
* @param toolCallId 工具调用ID
|
||||
* @param toolName 工具名称
|
||||
* @param approvalPrompt 审批文案
|
||||
* @param toolInput 工具入参
|
||||
* @param metadata 元数据
|
||||
* @param expiresAt 过期时间
|
||||
* @return 待审批状态
|
||||
*/
|
||||
public AgentPendingState register(String sessionId,
|
||||
String agentId,
|
||||
String toolCallId,
|
||||
String toolName,
|
||||
String approvalPrompt,
|
||||
Map<String, Object> toolInput,
|
||||
Map<String, Object> metadata,
|
||||
Instant expiresAt) {
|
||||
AgentPendingState state = new AgentPendingState();
|
||||
state.setSessionId(sessionId);
|
||||
state.setAgentId(agentId);
|
||||
state.setToolCallId(toolCallId);
|
||||
state.setToolName(toolName);
|
||||
state.setApprovalPrompt(approvalPrompt);
|
||||
state.setToolInput(toolInput);
|
||||
state.setMetadata(metadata);
|
||||
state.setExpiresAt(expiresAt);
|
||||
if (enabled) {
|
||||
String token = state.getResumeToken().getValue();
|
||||
PendingApproval pendingApproval = new PendingApproval(state, new CompletableFuture<>());
|
||||
approvals.put(token, pendingApproval);
|
||||
pendingApproval.future.whenComplete((response, error) -> approvals.remove(token));
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
/**
|
||||
* 等待指定审批令牌的响应。
|
||||
*
|
||||
* @param resumeToken 恢复令牌
|
||||
* @return 审批响应
|
||||
*/
|
||||
public Mono<AgentToolApprovalResponse> await(AgentResumeToken resumeToken) {
|
||||
if (!enabled) {
|
||||
AgentToolApprovalResponse response = new AgentToolApprovalResponse();
|
||||
response.setResumeToken(resumeToken);
|
||||
response.setApproved(true);
|
||||
return Mono.just(response);
|
||||
}
|
||||
if (resumeToken == null || resumeToken.getValue() == null || resumeToken.getValue().isBlank()) {
|
||||
return Mono.error(new AgentToolApprovalRejectedException("缺少审批令牌。"));
|
||||
}
|
||||
PendingApproval pendingApproval = approvals.get(resumeToken.getValue());
|
||||
if (pendingApproval == null) {
|
||||
return Mono.error(new AgentToolApprovalRejectedException("审批请求已失效。"));
|
||||
}
|
||||
return Mono.fromFuture(pendingApproval.future);
|
||||
}
|
||||
|
||||
/**
|
||||
* 提交审批响应。
|
||||
*
|
||||
* @param response 审批响应
|
||||
*/
|
||||
public void submit(AgentToolApprovalResponse response) {
|
||||
if (!enabled || response == null || response.getResumeToken() == null
|
||||
|| response.getResumeToken().getValue() == null) {
|
||||
return;
|
||||
}
|
||||
PendingApproval pendingApproval = approvals.get(response.getResumeToken().getValue());
|
||||
if (pendingApproval != null) {
|
||||
pendingApproval.future.complete(response);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消所有挂起审批。
|
||||
*
|
||||
* @param reason 取消原因
|
||||
*/
|
||||
public void cancelAll(String reason) {
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
for (PendingApproval pendingApproval : approvals.values()) {
|
||||
AgentToolApprovalResponse response = new AgentToolApprovalResponse();
|
||||
response.setResumeToken(pendingApproval.state.getResumeToken());
|
||||
response.setApproved(false);
|
||||
response.setRejectReason(reason);
|
||||
pendingApproval.future.complete(response);
|
||||
}
|
||||
approvals.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否已启用。
|
||||
*
|
||||
* @return 启用标记
|
||||
*/
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
private static class PendingApproval {
|
||||
private final AgentPendingState state;
|
||||
private final CompletableFuture<AgentToolApprovalResponse> future;
|
||||
|
||||
private PendingApproval(AgentPendingState state, CompletableFuture<AgentToolApprovalResponse> future) {
|
||||
this.state = Objects.requireNonNull(state, "state");
|
||||
this.future = Objects.requireNonNull(future, "future");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.easyagents.agent.runtime.hitl;
|
||||
|
||||
import com.easyagents.agent.runtime.AgentRuntimeException;
|
||||
|
||||
/**
|
||||
* 工具审批被拒绝时抛出的异常。
|
||||
*/
|
||||
public class AgentToolApprovalRejectedException extends AgentRuntimeException {
|
||||
|
||||
private final String rejectReason;
|
||||
|
||||
/**
|
||||
* 创建工具审批拒绝异常。
|
||||
*
|
||||
* @param rejectReason 拒绝原因
|
||||
*/
|
||||
public AgentToolApprovalRejectedException(String rejectReason) {
|
||||
super(rejectReason == null || rejectReason.isBlank() ? "工具执行已被拒绝。" : rejectReason);
|
||||
this.rejectReason = rejectReason;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取拒绝原因。
|
||||
*
|
||||
* @return 拒绝原因
|
||||
*/
|
||||
public String getRejectReason() {
|
||||
return rejectReason;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package com.easyagents.agent.runtime.hitl;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 工具执行审批请求。
|
||||
*/
|
||||
public class AgentToolApprovalRequest {
|
||||
|
||||
private String approvalPrompt;
|
||||
private Duration timeout = Duration.ofMinutes(30);
|
||||
private Map<String, Object> metadata = new LinkedHashMap<>();
|
||||
|
||||
/**
|
||||
* 获取审批提示文案。
|
||||
*
|
||||
* @return 审批提示文案
|
||||
*/
|
||||
public String getApprovalPrompt() {
|
||||
return approvalPrompt;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置审批提示文案。
|
||||
*
|
||||
* @param approvalPrompt 审批提示文案
|
||||
*/
|
||||
public void setApprovalPrompt(String approvalPrompt) {
|
||||
this.approvalPrompt = approvalPrompt;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取审批等待超时时间。
|
||||
*
|
||||
* @return 超时时间
|
||||
*/
|
||||
public Duration getTimeout() {
|
||||
return timeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置审批等待超时时间。
|
||||
*
|
||||
* @param timeout 超时时间
|
||||
*/
|
||||
public void setTimeout(Duration timeout) {
|
||||
this.timeout = timeout == null ? Duration.ofMinutes(30) : timeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取审批元数据。
|
||||
*
|
||||
* @return 审批元数据
|
||||
*/
|
||||
public Map<String, Object> getMetadata() {
|
||||
return metadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置审批元数据。
|
||||
*
|
||||
* @param metadata 审批元数据
|
||||
*/
|
||||
public void setMetadata(Map<String, Object> metadata) {
|
||||
this.metadata = metadata == null ? new LinkedHashMap<>() : metadata;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
package com.easyagents.agent.runtime.hitl;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 工具执行审批响应。
|
||||
*/
|
||||
public class AgentToolApprovalResponse {
|
||||
|
||||
private AgentResumeToken resumeToken;
|
||||
private boolean approved;
|
||||
private String rejectReason;
|
||||
private Map<String, Object> metadata = new LinkedHashMap<>();
|
||||
|
||||
/**
|
||||
* 获取恢复令牌。
|
||||
*
|
||||
* @return 恢复令牌
|
||||
*/
|
||||
public AgentResumeToken getResumeToken() {
|
||||
return resumeToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置恢复令牌。
|
||||
*
|
||||
* @param resumeToken 恢复令牌
|
||||
*/
|
||||
public void setResumeToken(AgentResumeToken resumeToken) {
|
||||
this.resumeToken = resumeToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回是否批准执行工具。
|
||||
*
|
||||
* @return 批准时为 true
|
||||
*/
|
||||
public boolean isApproved() {
|
||||
return approved;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置是否批准执行工具。
|
||||
*
|
||||
* @param approved 批准标记
|
||||
*/
|
||||
public void setApproved(boolean approved) {
|
||||
this.approved = approved;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取拒绝原因。
|
||||
*
|
||||
* @return 拒绝原因
|
||||
*/
|
||||
public String getRejectReason() {
|
||||
return rejectReason;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置拒绝原因。
|
||||
*
|
||||
* @param rejectReason 拒绝原因
|
||||
*/
|
||||
public void setRejectReason(String rejectReason) {
|
||||
this.rejectReason = rejectReason;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取审批元数据。
|
||||
*
|
||||
* @return 审批元数据
|
||||
*/
|
||||
public Map<String, Object> getMetadata() {
|
||||
return metadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置审批元数据。
|
||||
*
|
||||
* @param metadata 审批元数据
|
||||
*/
|
||||
public void setMetadata(Map<String, Object> metadata) {
|
||||
this.metadata = metadata == null ? new LinkedHashMap<>() : metadata;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
package com.easyagents.agent.runtime.knowledge;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 保留元数据的知识库检索文档。
|
||||
*/
|
||||
public class AgentKnowledgeDocument {
|
||||
|
||||
private String documentId;
|
||||
private String documentName;
|
||||
private String chunkId;
|
||||
private String content;
|
||||
private String sourceUri;
|
||||
private Double score;
|
||||
private Map<String, Object> knowledgeMetadata = new LinkedHashMap<>();
|
||||
private Map<String, Object> metadata = new LinkedHashMap<>();
|
||||
|
||||
/**
|
||||
* 获取文档ID。
|
||||
*
|
||||
* @return 文档ID
|
||||
*/
|
||||
public String getDocumentId() {
|
||||
return documentId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置文档ID。
|
||||
*
|
||||
* @param documentId 文档ID
|
||||
*/
|
||||
public void setDocumentId(String documentId) {
|
||||
this.documentId = documentId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文档名称。
|
||||
*
|
||||
* @return 文档名称
|
||||
*/
|
||||
public String getDocumentName() {
|
||||
return documentName;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置文档名称。
|
||||
*
|
||||
* @param documentName 文档名称
|
||||
*/
|
||||
public void setDocumentName(String documentName) {
|
||||
this.documentName = documentName;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取分片ID。
|
||||
*
|
||||
* @return 分片ID
|
||||
*/
|
||||
public String getChunkId() {
|
||||
return chunkId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置分片ID。
|
||||
*
|
||||
* @param chunkId 分片ID
|
||||
*/
|
||||
public void setChunkId(String chunkId) {
|
||||
this.chunkId = chunkId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取内容。
|
||||
*
|
||||
* @return 内容
|
||||
*/
|
||||
public String getContent() {
|
||||
return content;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置内容。
|
||||
*
|
||||
* @param content 内容
|
||||
*/
|
||||
public void setContent(String content) {
|
||||
this.content = content;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取来源 URI。
|
||||
*
|
||||
* @return 来源 URI
|
||||
*/
|
||||
public String getSourceUri() {
|
||||
return sourceUri;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置来源 URI。
|
||||
*
|
||||
* @param sourceUri 来源 URI
|
||||
*/
|
||||
public void setSourceUri(String sourceUri) {
|
||||
this.sourceUri = sourceUri;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取分数。
|
||||
*
|
||||
* @return 分数
|
||||
*/
|
||||
public Double getScore() {
|
||||
return score;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置分数。
|
||||
*
|
||||
* @param score 分数
|
||||
*/
|
||||
public void setScore(Double score) {
|
||||
this.score = score;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取知识库级元数据。
|
||||
*
|
||||
* @return 知识库级元数据
|
||||
*/
|
||||
public Map<String, Object> getKnowledgeMetadata() {
|
||||
return knowledgeMetadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置知识库级元数据。
|
||||
*
|
||||
* @param knowledgeMetadata 知识库级元数据
|
||||
*/
|
||||
public void setKnowledgeMetadata(Map<String, Object> knowledgeMetadata) {
|
||||
this.knowledgeMetadata = knowledgeMetadata == null ? new LinkedHashMap<>() : knowledgeMetadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文档级元数据。
|
||||
*
|
||||
* @return 文档级元数据
|
||||
*/
|
||||
public Map<String, Object> getMetadata() {
|
||||
return metadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置文档级元数据。
|
||||
*
|
||||
* @param metadata 文档级元数据
|
||||
*/
|
||||
public void setMetadata(Map<String, Object> metadata) {
|
||||
this.metadata = metadata == null ? new LinkedHashMap<>() : metadata;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.easyagents.agent.runtime.knowledge;
|
||||
|
||||
/**
|
||||
* 知识库检索策略。
|
||||
*/
|
||||
public enum AgentKnowledgePolicy {
|
||||
AGENTIC,
|
||||
GENERIC,
|
||||
DISABLED
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
package com.easyagents.agent.runtime.knowledge;
|
||||
|
||||
import com.easyagents.agent.runtime.AgentRuntimeContext;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 传递给知识库检索器的请求。
|
||||
*/
|
||||
public class AgentKnowledgeRetrievalRequest {
|
||||
|
||||
private String query;
|
||||
private int limit;
|
||||
private double scoreThreshold;
|
||||
private AgentKnowledgeSpec knowledgeSpec;
|
||||
private AgentRuntimeContext runtimeContext = new AgentRuntimeContext();
|
||||
private Map<String, Object> metadata = new LinkedHashMap<>();
|
||||
|
||||
/**
|
||||
* 获取查询。
|
||||
*
|
||||
* @return 查询
|
||||
*/
|
||||
public String getQuery() {
|
||||
return query;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置查询。
|
||||
*
|
||||
* @param query 查询
|
||||
*/
|
||||
public void setQuery(String query) {
|
||||
this.query = query;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取检索数量限制。
|
||||
*
|
||||
* @return 检索数量限制
|
||||
*/
|
||||
public int getLimit() {
|
||||
return limit;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置检索数量限制。
|
||||
*
|
||||
* @param limit 检索数量限制
|
||||
*/
|
||||
public void setLimit(int limit) {
|
||||
this.limit = limit;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取分数阈值。
|
||||
*
|
||||
* @return 分数阈值
|
||||
*/
|
||||
public double getScoreThreshold() {
|
||||
return scoreThreshold;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置分数阈值。
|
||||
*
|
||||
* @param scoreThreshold 分数阈值
|
||||
*/
|
||||
public void setScoreThreshold(double scoreThreshold) {
|
||||
this.scoreThreshold = scoreThreshold;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取知识库声明。
|
||||
*
|
||||
* @return 知识库声明
|
||||
*/
|
||||
public AgentKnowledgeSpec getKnowledgeSpec() {
|
||||
return knowledgeSpec;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置知识库声明。
|
||||
*
|
||||
* @param knowledgeSpec 知识库声明
|
||||
*/
|
||||
public void setKnowledgeSpec(AgentKnowledgeSpec knowledgeSpec) {
|
||||
this.knowledgeSpec = knowledgeSpec;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取运行时上下文。
|
||||
*
|
||||
* @return 运行时上下文
|
||||
*/
|
||||
public AgentRuntimeContext getRuntimeContext() {
|
||||
return runtimeContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置运行时上下文。
|
||||
*
|
||||
* @param runtimeContext 运行时上下文
|
||||
*/
|
||||
public void setRuntimeContext(AgentRuntimeContext runtimeContext) {
|
||||
this.runtimeContext = runtimeContext == null ? new AgentRuntimeContext() : runtimeContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取元数据。
|
||||
*
|
||||
* @return 元数据
|
||||
*/
|
||||
public Map<String, Object> getMetadata() {
|
||||
return metadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置元数据。
|
||||
*
|
||||
* @param metadata 元数据
|
||||
*/
|
||||
public void setMetadata(Map<String, Object> metadata) {
|
||||
this.metadata = metadata == null ? new LinkedHashMap<>() : metadata;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package com.easyagents.agent.runtime.knowledge;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 知识库检索结果。
|
||||
*/
|
||||
public class AgentKnowledgeRetrievalResult {
|
||||
|
||||
private List<AgentKnowledgeDocument> documents = new ArrayList<>();
|
||||
private Map<String, Object> metadata = new LinkedHashMap<>();
|
||||
|
||||
/**
|
||||
* 根据检索文档创建结果。
|
||||
*
|
||||
* @param documents 检索文档
|
||||
* @return 检索结果
|
||||
*/
|
||||
public static AgentKnowledgeRetrievalResult of(List<AgentKnowledgeDocument> documents) {
|
||||
AgentKnowledgeRetrievalResult result = new AgentKnowledgeRetrievalResult();
|
||||
result.setDocuments(documents);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文档列表。
|
||||
*
|
||||
* @return 文档列表
|
||||
*/
|
||||
public List<AgentKnowledgeDocument> getDocuments() {
|
||||
return documents;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置文档列表。
|
||||
*
|
||||
* @param documents 文档列表
|
||||
*/
|
||||
public void setDocuments(List<AgentKnowledgeDocument> documents) {
|
||||
this.documents = documents == null ? new ArrayList<>() : documents;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取元数据。
|
||||
*
|
||||
* @return 元数据
|
||||
*/
|
||||
public Map<String, Object> getMetadata() {
|
||||
return metadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置元数据。
|
||||
*
|
||||
* @param metadata 元数据
|
||||
*/
|
||||
public void setMetadata(Map<String, Object> metadata) {
|
||||
this.metadata = metadata == null ? new LinkedHashMap<>() : metadata;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.easyagents.agent.runtime.knowledge;
|
||||
|
||||
/**
|
||||
* 从单个知识源检索文档。
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface AgentKnowledgeRetriever {
|
||||
|
||||
/**
|
||||
* 检索相关文档。
|
||||
*
|
||||
* @param request 检索请求
|
||||
* @return 检索结果
|
||||
*/
|
||||
AgentKnowledgeRetrievalResult retrieve(AgentKnowledgeRetrievalRequest request);
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
package com.easyagents.agent.runtime.knowledge;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 知识库声明。
|
||||
*/
|
||||
public class AgentKnowledgeSpec {
|
||||
|
||||
private String knowledgeId;
|
||||
private String name;
|
||||
private String description;
|
||||
private AgentKnowledgePolicy retrievalMode = AgentKnowledgePolicy.AGENTIC;
|
||||
private int limit = 5;
|
||||
private double scoreThreshold = 0D;
|
||||
private Map<String, Object> metadata = new LinkedHashMap<>();
|
||||
|
||||
/**
|
||||
* 获取知识库ID。
|
||||
*
|
||||
* @return 知识库ID
|
||||
*/
|
||||
public String getKnowledgeId() {
|
||||
return knowledgeId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置知识库ID。
|
||||
*
|
||||
* @param knowledgeId 知识库ID
|
||||
*/
|
||||
public void setKnowledgeId(String knowledgeId) {
|
||||
this.knowledgeId = knowledgeId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取知识库名称。
|
||||
*
|
||||
* @return 知识库名称
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置知识库名称。
|
||||
*
|
||||
* @param name 知识库名称
|
||||
*/
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取描述。
|
||||
*
|
||||
* @return 描述
|
||||
*/
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置描述。
|
||||
*
|
||||
* @param description 描述
|
||||
*/
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取检索模式。
|
||||
*
|
||||
* @return 检索模式
|
||||
*/
|
||||
public AgentKnowledgePolicy getRetrievalMode() {
|
||||
return retrievalMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置检索模式。
|
||||
*
|
||||
* @param retrievalMode 检索模式
|
||||
*/
|
||||
public void setRetrievalMode(AgentKnowledgePolicy retrievalMode) {
|
||||
this.retrievalMode = retrievalMode == null ? AgentKnowledgePolicy.AGENTIC : retrievalMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取限制数量。
|
||||
*
|
||||
* @return 限制数量
|
||||
*/
|
||||
public int getLimit() {
|
||||
return limit;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置限制数量。
|
||||
*
|
||||
* @param limit 限制数量
|
||||
*/
|
||||
public void setLimit(int limit) {
|
||||
this.limit = limit <= 0 ? 5 : limit;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取分数阈值。
|
||||
*
|
||||
* @return 分数阈值
|
||||
*/
|
||||
public double getScoreThreshold() {
|
||||
return scoreThreshold;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置分数阈值。
|
||||
*
|
||||
* @param scoreThreshold 分数阈值
|
||||
*/
|
||||
public void setScoreThreshold(double scoreThreshold) {
|
||||
this.scoreThreshold = Math.max(0D, scoreThreshold);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取元数据。
|
||||
*
|
||||
* @return 元数据
|
||||
*/
|
||||
public Map<String, Object> getMetadata() {
|
||||
return metadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置元数据。
|
||||
*
|
||||
* @param metadata 元数据
|
||||
*/
|
||||
public void setMetadata(Map<String, Object> metadata) {
|
||||
this.metadata = metadata == null ? new LinkedHashMap<>() : metadata;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
package com.easyagents.agent.runtime.memory;
|
||||
|
||||
/**
|
||||
* AutoContext 自动压缩参数。
|
||||
*/
|
||||
public class AgentMemoryCompressionParameter {
|
||||
|
||||
private boolean enabled = true;
|
||||
private int msgThreshold = 20;
|
||||
private int lastKeep = 8;
|
||||
private double tokenRatio = 0.7D;
|
||||
private long maxToken = 12000L;
|
||||
private long largePayloadThreshold = 2048L;
|
||||
private int minCompressionTokenThreshold = 1000;
|
||||
private double currentRoundCompressionRatio = 0.5D;
|
||||
private int minConsecutiveToolMessages = 4;
|
||||
|
||||
/**
|
||||
* 判断是否启用自动压缩。
|
||||
*
|
||||
* @return 启用时返回 true
|
||||
*/
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置压缩开关。
|
||||
*
|
||||
* @param enabled 压缩开关
|
||||
*/
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取消息阈值。
|
||||
*
|
||||
* @return 消息阈值
|
||||
*/
|
||||
public int getMsgThreshold() {
|
||||
return msgThreshold;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置消息阈值。
|
||||
*
|
||||
* @param msgThreshold 消息阈值
|
||||
*/
|
||||
public void setMsgThreshold(int msgThreshold) {
|
||||
this.msgThreshold = msgThreshold <= 0 ? 20 : msgThreshold;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取最近保留消息数。
|
||||
*
|
||||
* @return 最近保留消息数
|
||||
*/
|
||||
public int getLastKeep() {
|
||||
return lastKeep;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置最近保留消息数。
|
||||
*
|
||||
* @param lastKeep 最近保留消息数
|
||||
*/
|
||||
public void setLastKeep(int lastKeep) {
|
||||
this.lastKeep = Math.max(0, lastKeep);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取Token 比例。
|
||||
*
|
||||
* @return Token 比例
|
||||
*/
|
||||
public double getTokenRatio() {
|
||||
return tokenRatio;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置Token 比例。
|
||||
*
|
||||
* @param tokenRatio Token 比例
|
||||
*/
|
||||
public void setTokenRatio(double tokenRatio) {
|
||||
this.tokenRatio = tokenRatio <= 0D ? 0.7D : tokenRatio;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取最大 Token 数。
|
||||
*
|
||||
* @return 最大 Token 数
|
||||
*/
|
||||
public long getMaxToken() {
|
||||
return maxToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置最大 Token 数。
|
||||
*
|
||||
* @param maxToken 最大 Token 数
|
||||
*/
|
||||
public void setMaxToken(long maxToken) {
|
||||
this.maxToken = maxToken <= 0L ? 12000L : maxToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取大负载阈值。
|
||||
*
|
||||
* @return 大负载阈值
|
||||
*/
|
||||
public long getLargePayloadThreshold() {
|
||||
return largePayloadThreshold;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置大负载阈值。
|
||||
*
|
||||
* @param largePayloadThreshold 大负载阈值
|
||||
*/
|
||||
public void setLargePayloadThreshold(long largePayloadThreshold) {
|
||||
this.largePayloadThreshold = largePayloadThreshold <= 0L ? 2048L : largePayloadThreshold;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取最小压缩 Token 阈值。
|
||||
*
|
||||
* @return 最小压缩 Token 阈值
|
||||
*/
|
||||
public int getMinCompressionTokenThreshold() {
|
||||
return minCompressionTokenThreshold;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置最小压缩 Token 阈值。
|
||||
*
|
||||
* @param minCompressionTokenThreshold 最小压缩 Token 阈值
|
||||
*/
|
||||
public void setMinCompressionTokenThreshold(int minCompressionTokenThreshold) {
|
||||
this.minCompressionTokenThreshold = Math.max(0, minCompressionTokenThreshold);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前轮压缩比例。
|
||||
*
|
||||
* @return 当前轮压缩比例
|
||||
*/
|
||||
public double getCurrentRoundCompressionRatio() {
|
||||
return currentRoundCompressionRatio;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置当前轮压缩比例。
|
||||
*
|
||||
* @param currentRoundCompressionRatio 当前轮压缩比例
|
||||
*/
|
||||
public void setCurrentRoundCompressionRatio(double currentRoundCompressionRatio) {
|
||||
this.currentRoundCompressionRatio = currentRoundCompressionRatio <= 0D ? 0.5D : currentRoundCompressionRatio;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取最小连续工具消息数。
|
||||
*
|
||||
* @return 最小连续工具消息数
|
||||
*/
|
||||
public int getMinConsecutiveToolMessages() {
|
||||
return minConsecutiveToolMessages;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置最小连续工具消息数。
|
||||
*
|
||||
* @param minConsecutiveToolMessages 最小连续工具消息数
|
||||
*/
|
||||
public void setMinConsecutiveToolMessages(int minConsecutiveToolMessages) {
|
||||
this.minConsecutiveToolMessages = Math.max(0, minConsecutiveToolMessages);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
package com.easyagents.agent.runtime.memory;
|
||||
|
||||
/**
|
||||
* 运行时记忆策略。
|
||||
*/
|
||||
public class AgentMemoryPolicy {
|
||||
|
||||
private AgentMemoryType type = AgentMemoryType.AUTO_CONTEXT;
|
||||
private int maxAttachedMessageCount = 50;
|
||||
private AgentMemoryCompressionParameter compressionParameter = new AgentMemoryCompressionParameter();
|
||||
|
||||
/**
|
||||
* 创建默认 AutoContext 记忆策略。
|
||||
*
|
||||
* @return AutoContext 记忆策略
|
||||
*/
|
||||
public static AgentMemoryPolicy autoContext() {
|
||||
AgentMemoryPolicy policy = new AgentMemoryPolicy();
|
||||
policy.setType(AgentMemoryType.AUTO_CONTEXT);
|
||||
return policy;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建内存策略。
|
||||
*
|
||||
* @return 内存策略
|
||||
*/
|
||||
public static AgentMemoryPolicy inMemory() {
|
||||
AgentMemoryPolicy policy = new AgentMemoryPolicy();
|
||||
policy.setType(AgentMemoryType.IN_MEMORY);
|
||||
return policy;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取记忆类型。
|
||||
*
|
||||
* @return 记忆类型
|
||||
*/
|
||||
public AgentMemoryType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置记忆类型。
|
||||
*
|
||||
* @param type 记忆类型
|
||||
*/
|
||||
public void setType(AgentMemoryType type) {
|
||||
this.type = type == null ? AgentMemoryType.AUTO_CONTEXT : type;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取最大附加消息数。
|
||||
*
|
||||
* @return 最大附加消息数
|
||||
*/
|
||||
public int getMaxAttachedMessageCount() {
|
||||
return maxAttachedMessageCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置最大附加消息数。
|
||||
*
|
||||
* @param maxAttachedMessageCount 最大附加消息数
|
||||
*/
|
||||
public void setMaxAttachedMessageCount(int maxAttachedMessageCount) {
|
||||
this.maxAttachedMessageCount = maxAttachedMessageCount <= 0 ? 50 : maxAttachedMessageCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取自动压缩参数。
|
||||
*
|
||||
* @return 自动压缩参数
|
||||
*/
|
||||
public AgentMemoryCompressionParameter getCompressionParameter() {
|
||||
return compressionParameter;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置自动压缩参数。
|
||||
*
|
||||
* @param compressionParameter 自动压缩参数
|
||||
*/
|
||||
public void setCompressionParameter(AgentMemoryCompressionParameter compressionParameter) {
|
||||
this.compressionParameter = compressionParameter == null
|
||||
? new AgentMemoryCompressionParameter()
|
||||
: compressionParameter;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package com.easyagents.agent.runtime.memory;
|
||||
|
||||
import com.easyagents.agent.runtime.message.AgentMessage;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 由调用方恢复的会话历史快照。
|
||||
*/
|
||||
public class AgentMemorySnapshot {
|
||||
|
||||
private List<AgentMessage> messages = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* 获取消息列表。
|
||||
*
|
||||
* @return 消息列表
|
||||
*/
|
||||
public List<AgentMessage> getMessages() {
|
||||
return messages;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置消息列表。
|
||||
*
|
||||
* @param messages 消息列表
|
||||
*/
|
||||
public void setMessages(List<AgentMessage> messages) {
|
||||
this.messages = messages == null ? new ArrayList<>() : messages;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加one message。
|
||||
*
|
||||
* @param message 消息
|
||||
*/
|
||||
public void addMessage(AgentMessage message) {
|
||||
if (message != null) {
|
||||
messages.add(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.easyagents.agent.runtime.memory;
|
||||
|
||||
/**
|
||||
* 记忆实现类型。
|
||||
*/
|
||||
public enum AgentMemoryType {
|
||||
IN_MEMORY,
|
||||
AUTO_CONTEXT
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.easyagents.agent.runtime.memory;
|
||||
|
||||
/**
|
||||
* 中立消息角色。
|
||||
*/
|
||||
public enum AgentMessageRole {
|
||||
SYSTEM,
|
||||
USER,
|
||||
ASSISTANT,
|
||||
TOOL
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package com.easyagents.agent.runtime.message;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 运行时消息内容块。
|
||||
*/
|
||||
public abstract class AgentContentBlock {
|
||||
|
||||
private final AgentContentBlockType type;
|
||||
private final Map<String, Object> metadata = new LinkedHashMap<>();
|
||||
|
||||
/**
|
||||
* 创建内容块。
|
||||
*
|
||||
* @param type 块类型
|
||||
*/
|
||||
protected AgentContentBlock(AgentContentBlockType type) {
|
||||
this.type = type == null ? AgentContentBlockType.UNKNOWN : type;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取块类型。
|
||||
*
|
||||
* @return 块类型
|
||||
*/
|
||||
public AgentContentBlockType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取元数据。
|
||||
*
|
||||
* @return 元数据
|
||||
*/
|
||||
public Map<String, Object> getMetadata() {
|
||||
return metadata;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.easyagents.agent.runtime.message;
|
||||
|
||||
/**
|
||||
* 消息块类型。
|
||||
*/
|
||||
public enum AgentContentBlockType {
|
||||
TEXT,
|
||||
THINKING,
|
||||
TOOL_USE,
|
||||
TOOL_RESULT,
|
||||
IMAGE,
|
||||
AUDIO,
|
||||
VIDEO,
|
||||
UNKNOWN
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
package com.easyagents.agent.runtime.message;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 知识库引用信息。
|
||||
*/
|
||||
public class AgentKnowledgeReference {
|
||||
|
||||
private String knowledgeId;
|
||||
private String knowledgeName;
|
||||
private String documentId;
|
||||
private String documentName;
|
||||
private String chunkId;
|
||||
private String sourceUri;
|
||||
private Double score;
|
||||
private Map<String, Object> metadata = new LinkedHashMap<>();
|
||||
|
||||
/**
|
||||
* 获取知识库ID。
|
||||
*
|
||||
* @return 知识库ID
|
||||
*/
|
||||
public String getKnowledgeId() {
|
||||
return knowledgeId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置知识库ID。
|
||||
*
|
||||
* @param knowledgeId 知识库ID
|
||||
*/
|
||||
public void setKnowledgeId(String knowledgeId) {
|
||||
this.knowledgeId = knowledgeId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取知识库名称。
|
||||
*
|
||||
* @return 知识库名称
|
||||
*/
|
||||
public String getKnowledgeName() {
|
||||
return knowledgeName;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置知识库名称。
|
||||
*
|
||||
* @param knowledgeName 知识库名称
|
||||
*/
|
||||
public void setKnowledgeName(String knowledgeName) {
|
||||
this.knowledgeName = knowledgeName;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文档ID。
|
||||
*
|
||||
* @return 文档ID
|
||||
*/
|
||||
public String getDocumentId() {
|
||||
return documentId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置文档ID。
|
||||
*
|
||||
* @param documentId 文档ID
|
||||
*/
|
||||
public void setDocumentId(String documentId) {
|
||||
this.documentId = documentId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文档名称。
|
||||
*
|
||||
* @return 文档名称
|
||||
*/
|
||||
public String getDocumentName() {
|
||||
return documentName;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置文档名称。
|
||||
*
|
||||
* @param documentName 文档名称
|
||||
*/
|
||||
public void setDocumentName(String documentName) {
|
||||
this.documentName = documentName;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取分片ID。
|
||||
*
|
||||
* @return 分片ID
|
||||
*/
|
||||
public String getChunkId() {
|
||||
return chunkId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置分片ID。
|
||||
*
|
||||
* @param chunkId 分片ID
|
||||
*/
|
||||
public void setChunkId(String chunkId) {
|
||||
this.chunkId = chunkId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取来源 URI。
|
||||
*
|
||||
* @return 来源 URI
|
||||
*/
|
||||
public String getSourceUri() {
|
||||
return sourceUri;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置来源 URI。
|
||||
*
|
||||
* @param sourceUri 来源 URI
|
||||
*/
|
||||
public void setSourceUri(String sourceUri) {
|
||||
this.sourceUri = sourceUri;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取分数。
|
||||
*
|
||||
* @return 分数
|
||||
*/
|
||||
public Double getScore() {
|
||||
return score;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置分数。
|
||||
*
|
||||
* @param score 分数
|
||||
*/
|
||||
public void setScore(Double score) {
|
||||
this.score = score;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取元数据。
|
||||
*
|
||||
* @return 元数据
|
||||
*/
|
||||
public Map<String, Object> getMetadata() {
|
||||
return metadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置元数据。
|
||||
*
|
||||
* @param metadata 元数据
|
||||
*/
|
||||
public void setMetadata(Map<String, Object> metadata) {
|
||||
this.metadata = metadata == null ? new LinkedHashMap<>() : metadata;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,221 @@
|
||||
package com.easyagents.agent.runtime.message;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 媒体内容块。
|
||||
*/
|
||||
public class AgentMediaBlock extends AgentContentBlock {
|
||||
|
||||
private String mimeType;
|
||||
private String url;
|
||||
private String data;
|
||||
private Integer minPixels;
|
||||
private Integer maxPixels;
|
||||
private Float fps;
|
||||
private Integer maxFrames;
|
||||
private Integer totalPixels;
|
||||
private String mediaKind;
|
||||
|
||||
/**
|
||||
* 创建媒体块。
|
||||
*
|
||||
* @param mediaKind 媒体类型
|
||||
*/
|
||||
public AgentMediaBlock(String mediaKind) {
|
||||
super(resolveType(mediaKind));
|
||||
this.mediaKind = mediaKind;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取媒体类型。
|
||||
*
|
||||
* @return 媒体类型
|
||||
*/
|
||||
public String getMediaKind() {
|
||||
return mediaKind;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置媒体类型。
|
||||
*
|
||||
* @param mediaKind 媒体类型
|
||||
*/
|
||||
public void setMediaKind(String mediaKind) {
|
||||
this.mediaKind = mediaKind;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 MIME 类型。
|
||||
*
|
||||
* @return MIME 类型
|
||||
*/
|
||||
public String getMimeType() {
|
||||
return mimeType;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 MIME 类型。
|
||||
*
|
||||
* @param mimeType MIME 类型
|
||||
*/
|
||||
public void setMimeType(String mimeType) {
|
||||
this.mimeType = mimeType;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 URL。
|
||||
*
|
||||
* @return URL
|
||||
*/
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 URL。
|
||||
*
|
||||
* @param url URL
|
||||
*/
|
||||
public void setUrl(String url) {
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 Base64 数据。
|
||||
*
|
||||
* @return Base64 数据
|
||||
*/
|
||||
public String getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 Base64 数据。
|
||||
*
|
||||
* @param data Base64 数据
|
||||
*/
|
||||
public void setData(String data) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取最小像素。
|
||||
*
|
||||
* @return 最小像素
|
||||
*/
|
||||
public Integer getMinPixels() {
|
||||
return minPixels;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置最小像素。
|
||||
*
|
||||
* @param minPixels 最小像素
|
||||
*/
|
||||
public void setMinPixels(Integer minPixels) {
|
||||
this.minPixels = minPixels;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取最大像素。
|
||||
*
|
||||
* @return 最大像素
|
||||
*/
|
||||
public Integer getMaxPixels() {
|
||||
return maxPixels;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置最大像素。
|
||||
*
|
||||
* @param maxPixels 最大像素
|
||||
*/
|
||||
public void setMaxPixels(Integer maxPixels) {
|
||||
this.maxPixels = maxPixels;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取帧率。
|
||||
*
|
||||
* @return 帧率
|
||||
*/
|
||||
public Float getFps() {
|
||||
return fps;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置帧率。
|
||||
*
|
||||
* @param fps 帧率
|
||||
*/
|
||||
public void setFps(Float fps) {
|
||||
this.fps = fps;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取最大帧数。
|
||||
*
|
||||
* @return 最大帧数
|
||||
*/
|
||||
public Integer getMaxFrames() {
|
||||
return maxFrames;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置最大帧数。
|
||||
*
|
||||
* @param maxFrames 最大帧数
|
||||
*/
|
||||
public void setMaxFrames(Integer maxFrames) {
|
||||
this.maxFrames = maxFrames;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取总像素。
|
||||
*
|
||||
* @return 总像素
|
||||
*/
|
||||
public Integer getTotalPixels() {
|
||||
return totalPixels;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置总像素。
|
||||
*
|
||||
* @param totalPixels 总像素
|
||||
*/
|
||||
public void setTotalPixels(Integer totalPixels) {
|
||||
this.totalPixels = totalPixels;
|
||||
}
|
||||
|
||||
private static AgentContentBlockType resolveType(String mediaKind) {
|
||||
if ("audio".equalsIgnoreCase(mediaKind)) {
|
||||
return AgentContentBlockType.AUDIO;
|
||||
}
|
||||
if ("video".equalsIgnoreCase(mediaKind)) {
|
||||
return AgentContentBlockType.VIDEO;
|
||||
}
|
||||
return AgentContentBlockType.IMAGE;
|
||||
}
|
||||
|
||||
/**
|
||||
* 额外元数据。
|
||||
*
|
||||
* @return 额外元数据
|
||||
*/
|
||||
public Map<String, Object> toMetadata() {
|
||||
Map<String, Object> metadata = new LinkedHashMap<>(getMetadata());
|
||||
metadata.put("mediaKind", mediaKind);
|
||||
metadata.put("mimeType", mimeType);
|
||||
metadata.put("url", url);
|
||||
metadata.put("data", data);
|
||||
metadata.put("minPixels", minPixels);
|
||||
metadata.put("maxPixels", maxPixels);
|
||||
metadata.put("fps", fps);
|
||||
metadata.put("maxFrames", maxFrames);
|
||||
metadata.put("totalPixels", totalPixels);
|
||||
return metadata;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
package com.easyagents.agent.runtime.message;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* 智能体聊天消息
|
||||
*/
|
||||
public class AgentMessage {
|
||||
|
||||
private String messageId = UUID.randomUUID().toString();
|
||||
private String name;
|
||||
/**
|
||||
* 聊天发起者角色
|
||||
*/
|
||||
private AgentMessageRole role = AgentMessageRole.USER;
|
||||
/**
|
||||
* 消息内容块
|
||||
*/
|
||||
private List<AgentContentBlock> contentBlocks = new ArrayList<>();
|
||||
/**
|
||||
* 知识库引用
|
||||
*/
|
||||
private List<AgentKnowledgeReference> knowledgeReferences = new ArrayList<>();
|
||||
/**
|
||||
* 元数据
|
||||
*/
|
||||
private Map<String, Object> metadata = new LinkedHashMap<>();
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private Instant createdAt = Instant.now();
|
||||
|
||||
/**
|
||||
* 创建纯文本消息。
|
||||
*
|
||||
* @param role 消息角色
|
||||
* @param content 文本内容
|
||||
* @return 消息
|
||||
*/
|
||||
public static AgentMessage text(AgentMessageRole role, String content) {
|
||||
AgentMessage message = new AgentMessage();
|
||||
message.setRole(role);
|
||||
message.setContentBlocks(List.of(new AgentTextBlock(content)));
|
||||
return message;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取消息ID。
|
||||
*
|
||||
* @return 消息ID
|
||||
*/
|
||||
public String getMessageId() {
|
||||
return messageId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置消息ID。
|
||||
*
|
||||
* @param messageId 消息ID
|
||||
*/
|
||||
public void setMessageId(String messageId) {
|
||||
this.messageId = messageId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取消息名称。
|
||||
*
|
||||
* @return 消息名称
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置消息名称。
|
||||
*
|
||||
* @param name 消息名称
|
||||
*/
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取角色。
|
||||
*
|
||||
* @return 角色
|
||||
*/
|
||||
public AgentMessageRole getRole() {
|
||||
return role;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置角色。
|
||||
*
|
||||
* @param role 角色
|
||||
*/
|
||||
public void setRole(AgentMessageRole role) {
|
||||
this.role = role == null ? AgentMessageRole.USER : role;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取内容块列表。
|
||||
*
|
||||
* @return 内容块列表
|
||||
*/
|
||||
public List<AgentContentBlock> getContentBlocks() {
|
||||
return contentBlocks;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置内容块列表。
|
||||
*
|
||||
* @param contentBlocks 内容块列表
|
||||
*/
|
||||
public void setContentBlocks(List<AgentContentBlock> contentBlocks) {
|
||||
this.contentBlocks = contentBlocks == null ? new ArrayList<>() : new ArrayList<>(contentBlocks);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取知识库引用。
|
||||
*
|
||||
* @return 知识库引用
|
||||
*/
|
||||
public List<AgentKnowledgeReference> getKnowledgeReferences() {
|
||||
return knowledgeReferences;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置知识库引用。
|
||||
*
|
||||
* @param knowledgeReferences 知识库引用
|
||||
*/
|
||||
public void setKnowledgeReferences(List<AgentKnowledgeReference> knowledgeReferences) {
|
||||
this.knowledgeReferences = knowledgeReferences == null ? new ArrayList<>() : new ArrayList<>(knowledgeReferences);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取元数据。
|
||||
*
|
||||
* @return 元数据
|
||||
*/
|
||||
public Map<String, Object> getMetadata() {
|
||||
return metadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置元数据。
|
||||
*
|
||||
* @param metadata 元数据
|
||||
*/
|
||||
public void setMetadata(Map<String, Object> metadata) {
|
||||
this.metadata = metadata == null ? new LinkedHashMap<>() : new LinkedHashMap<>(metadata);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取创建时间。
|
||||
*
|
||||
* @return 创建时间
|
||||
*/
|
||||
public Instant getCreatedAt() {
|
||||
return createdAt;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置创建时间。
|
||||
*
|
||||
* @param createdAt 创建时间
|
||||
*/
|
||||
public void setCreatedAt(Instant createdAt) {
|
||||
this.createdAt = createdAt == null ? Instant.now() : createdAt;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.easyagents.agent.runtime.message;
|
||||
|
||||
/**
|
||||
* 中立消息角色。
|
||||
*/
|
||||
public enum AgentMessageRole {
|
||||
SYSTEM,
|
||||
USER,
|
||||
ASSISTANT,
|
||||
TOOL
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.easyagents.agent.runtime.message;
|
||||
|
||||
/**
|
||||
* 文本内容块。
|
||||
*/
|
||||
public class AgentTextBlock extends AgentContentBlock {
|
||||
|
||||
private String text;
|
||||
|
||||
/**
|
||||
* 创建文本块。
|
||||
*
|
||||
* @param text 文本内容
|
||||
*/
|
||||
public AgentTextBlock(String text) {
|
||||
super(AgentContentBlockType.TEXT);
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文本。
|
||||
*
|
||||
* @return 文本
|
||||
*/
|
||||
public String getText() {
|
||||
return text;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置文本。
|
||||
*
|
||||
* @param text 文本
|
||||
*/
|
||||
public void setText(String text) {
|
||||
this.text = text;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.easyagents.agent.runtime.message;
|
||||
|
||||
/**
|
||||
* 思考内容块。
|
||||
*/
|
||||
public class AgentThinkingBlock extends AgentContentBlock {
|
||||
|
||||
private String thinking;
|
||||
|
||||
/**
|
||||
* 创建思考块。
|
||||
*
|
||||
* @param thinking 思考内容
|
||||
*/
|
||||
public AgentThinkingBlock(String thinking) {
|
||||
super(AgentContentBlockType.THINKING);
|
||||
this.thinking = thinking;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取思考内容。
|
||||
*
|
||||
* @return 思考内容
|
||||
*/
|
||||
public String getThinking() {
|
||||
return thinking;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置思考内容。
|
||||
*
|
||||
* @param thinking 思考内容
|
||||
*/
|
||||
public void setThinking(String thinking) {
|
||||
this.thinking = thinking;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
package com.easyagents.agent.runtime.message;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 工具结果内容块。
|
||||
*/
|
||||
public class AgentToolResultBlock extends AgentContentBlock {
|
||||
|
||||
private String id;
|
||||
private String name;
|
||||
private List<AgentContentBlock> output = new ArrayList<>();
|
||||
private boolean suspended;
|
||||
|
||||
/**
|
||||
* 创建工具结果块。
|
||||
*
|
||||
* @param id 调用ID
|
||||
* @param name 工具名
|
||||
*/
|
||||
public AgentToolResultBlock(String id, String name) {
|
||||
super(AgentContentBlockType.TOOL_RESULT);
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取调用ID。
|
||||
*
|
||||
* @return 调用ID
|
||||
*/
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置调用ID。
|
||||
*
|
||||
* @param id 调用ID
|
||||
*/
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取工具名。
|
||||
*
|
||||
* @return 工具名
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置工具名。
|
||||
*
|
||||
* @param name 工具名
|
||||
*/
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取输出块列表。
|
||||
*
|
||||
* @return 输出块列表
|
||||
*/
|
||||
public List<AgentContentBlock> getOutput() {
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置输出块列表。
|
||||
*
|
||||
* @param output 输出块列表
|
||||
*/
|
||||
public void setOutput(List<AgentContentBlock> output) {
|
||||
this.output = output == null ? new ArrayList<>() : new ArrayList<>(output);
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否挂起。
|
||||
*
|
||||
* @return 是否挂起
|
||||
*/
|
||||
public boolean isSuspended() {
|
||||
return suspended;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置挂起状态。
|
||||
*
|
||||
* @param suspended 挂起状态
|
||||
*/
|
||||
public void setSuspended(boolean suspended) {
|
||||
this.suspended = suspended;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
package com.easyagents.agent.runtime.message;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 工具调用内容块。
|
||||
*/
|
||||
public class AgentToolUseBlock extends AgentContentBlock {
|
||||
|
||||
private String id;
|
||||
private String name;
|
||||
private Map<String, Object> input = new LinkedHashMap<>();
|
||||
private String content;
|
||||
|
||||
/**
|
||||
* 创建工具调用块。
|
||||
*
|
||||
* @param id 调用ID
|
||||
* @param name 工具名
|
||||
* @param input 输入参数
|
||||
*/
|
||||
public AgentToolUseBlock(String id, String name, Map<String, Object> input) {
|
||||
super(AgentContentBlockType.TOOL_USE);
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
setInput(input);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取调用ID。
|
||||
*
|
||||
* @return 调用ID
|
||||
*/
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置调用ID。
|
||||
*
|
||||
* @param id 调用ID
|
||||
*/
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取工具名。
|
||||
*
|
||||
* @return 工具名
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置工具名。
|
||||
*
|
||||
* @param name 工具名
|
||||
*/
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取输入参数。
|
||||
*
|
||||
* @return 输入参数
|
||||
*/
|
||||
public Map<String, Object> getInput() {
|
||||
return input;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置输入参数。
|
||||
*
|
||||
* @param input 输入参数
|
||||
*/
|
||||
public void setInput(Map<String, Object> input) {
|
||||
this.input = input == null ? new LinkedHashMap<>() : new LinkedHashMap<>(input);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取原始内容。
|
||||
*
|
||||
* @return 原始内容
|
||||
*/
|
||||
public String getContent() {
|
||||
return content;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置原始内容。
|
||||
*
|
||||
* @param content 原始内容
|
||||
*/
|
||||
public void setContent(String content) {
|
||||
this.content = content;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
package com.easyagents.agent.runtime.message;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 未识别的内容消息块
|
||||
* 兜底表示。
|
||||
*/
|
||||
public class AgentUnknownBlock extends AgentContentBlock {
|
||||
|
||||
private String sourceClassName;
|
||||
private String sourceTypeName;
|
||||
private String rawText;
|
||||
private Map<String, Object> rawMetadata = new LinkedHashMap<>();
|
||||
|
||||
/**
|
||||
* 创建未知内容块。
|
||||
*/
|
||||
public AgentUnknownBlock() {
|
||||
super(AgentContentBlockType.UNKNOWN);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取来源类名。
|
||||
*
|
||||
* @return 来源类名
|
||||
*/
|
||||
public String getSourceClassName() {
|
||||
return sourceClassName;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置来源类名。
|
||||
*
|
||||
* @param sourceClassName 来源类名
|
||||
*/
|
||||
public void setSourceClassName(String sourceClassName) {
|
||||
this.sourceClassName = sourceClassName;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取来源类型名。
|
||||
*
|
||||
* @return 来源类型名
|
||||
*/
|
||||
public String getSourceTypeName() {
|
||||
return sourceTypeName;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置来源类型名。
|
||||
*
|
||||
* @param sourceTypeName 来源类型名
|
||||
*/
|
||||
public void setSourceTypeName(String sourceTypeName) {
|
||||
this.sourceTypeName = sourceTypeName;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取原始文本表示。
|
||||
*
|
||||
* @return 原始文本表示
|
||||
*/
|
||||
public String getRawText() {
|
||||
return rawText;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置原始文本表示。
|
||||
*
|
||||
* @param rawText 原始文本表示
|
||||
*/
|
||||
public void setRawText(String rawText) {
|
||||
this.rawText = rawText;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取原始元数据。
|
||||
*
|
||||
* @return 原始元数据
|
||||
*/
|
||||
public Map<String, Object> getRawMetadata() {
|
||||
return rawMetadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置原始元数据。
|
||||
*
|
||||
* @param rawMetadata 原始元数据
|
||||
*/
|
||||
public void setRawMetadata(Map<String, Object> rawMetadata) {
|
||||
this.rawMetadata = rawMetadata == null ? new LinkedHashMap<>() : new LinkedHashMap<>(rawMetadata);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,239 @@
|
||||
package com.easyagents.agent.runtime.model;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 运行时模型适配器使用的生成参数。
|
||||
*/
|
||||
public class AgentGenerationOptions {
|
||||
|
||||
private Double temperature;
|
||||
private Double topP;
|
||||
private Integer topK;
|
||||
private Integer maxTokens;
|
||||
private Integer maxCompletionTokens;
|
||||
private Integer thinkingBudget;
|
||||
private String reasoningEffort;
|
||||
private Boolean thinkingEnabled;
|
||||
private Boolean stream = true;
|
||||
private Map<String, Object> additionalBodyParams = new LinkedHashMap<>();
|
||||
private Map<String, String> additionalHeaders = new LinkedHashMap<>();
|
||||
private Map<String, String> additionalQueryParams = new LinkedHashMap<>();
|
||||
|
||||
/**
|
||||
* 获取temperature。
|
||||
*
|
||||
* @return temperature 参数
|
||||
*/
|
||||
public Double getTemperature() {
|
||||
return temperature;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置temperature。
|
||||
*
|
||||
* @param temperature temperature 参数
|
||||
*/
|
||||
public void setTemperature(Double temperature) {
|
||||
this.temperature = temperature;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取top-p。
|
||||
*
|
||||
* @return top-p 参数
|
||||
*/
|
||||
public Double getTopP() {
|
||||
return topP;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置top-p。
|
||||
*
|
||||
* @param topP top-p 参数
|
||||
*/
|
||||
public void setTopP(Double topP) {
|
||||
this.topP = topP;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取top-k。
|
||||
*
|
||||
* @return top-k 参数
|
||||
*/
|
||||
public Integer getTopK() {
|
||||
return topK;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置top-k。
|
||||
*
|
||||
* @param topK top-k 参数
|
||||
*/
|
||||
public void setTopK(Integer topK) {
|
||||
this.topK = topK;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取最大 Token 数。
|
||||
*
|
||||
* @return 最大 Token 数
|
||||
*/
|
||||
public Integer getMaxTokens() {
|
||||
return maxTokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置最大 Token 数。
|
||||
*
|
||||
* @param maxTokens 最大 Token 数
|
||||
*/
|
||||
public void setMaxTokens(Integer maxTokens) {
|
||||
this.maxTokens = maxTokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取最大补全 Token 数。
|
||||
*
|
||||
* @return 最大补全 Token 数
|
||||
*/
|
||||
public Integer getMaxCompletionTokens() {
|
||||
return maxCompletionTokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置最大补全 Token 数。
|
||||
*
|
||||
* @param maxCompletionTokens 最大补全 Token 数
|
||||
*/
|
||||
public void setMaxCompletionTokens(Integer maxCompletionTokens) {
|
||||
this.maxCompletionTokens = maxCompletionTokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取思考预算。
|
||||
*
|
||||
* @return 思考预算
|
||||
*/
|
||||
public Integer getThinkingBudget() {
|
||||
return thinkingBudget;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置思考预算。
|
||||
*
|
||||
* @param thinkingBudget 思考预算
|
||||
*/
|
||||
public void setThinkingBudget(Integer thinkingBudget) {
|
||||
this.thinkingBudget = thinkingBudget;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取推理强度。
|
||||
*
|
||||
* @return 推理强度
|
||||
*/
|
||||
public String getReasoningEffort() {
|
||||
return reasoningEffort;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置推理强度。
|
||||
*
|
||||
* @param reasoningEffort 推理强度
|
||||
*/
|
||||
public void setReasoningEffort(String reasoningEffort) {
|
||||
this.reasoningEffort = reasoningEffort;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回是否provider thinking is enabled。
|
||||
*
|
||||
* @return 思考开关
|
||||
*/
|
||||
public Boolean getThinkingEnabled() {
|
||||
return thinkingEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置provider thinking flag。
|
||||
*
|
||||
* @param thinkingEnabled 思考开关
|
||||
*/
|
||||
public void setThinkingEnabled(Boolean thinkingEnabled) {
|
||||
this.thinkingEnabled = thinkingEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回是否streaming is requested。
|
||||
*
|
||||
* @return 流式开关
|
||||
*/
|
||||
public Boolean getStream() {
|
||||
return stream;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置流式开关。
|
||||
*
|
||||
* @param stream 流式开关
|
||||
*/
|
||||
public void setStream(Boolean stream) {
|
||||
this.stream = stream;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取额外请求体参数。
|
||||
*
|
||||
* @return 额外请求体参数
|
||||
*/
|
||||
public Map<String, Object> getAdditionalBodyParams() {
|
||||
return additionalBodyParams;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置额外请求体参数。
|
||||
*
|
||||
* @param additionalBodyParams 额外请求体参数
|
||||
*/
|
||||
public void setAdditionalBodyParams(Map<String, Object> additionalBodyParams) {
|
||||
this.additionalBodyParams = additionalBodyParams == null ? new LinkedHashMap<>() : additionalBodyParams;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取额外请求头。
|
||||
*
|
||||
* @return 额外请求头
|
||||
*/
|
||||
public Map<String, String> getAdditionalHeaders() {
|
||||
return additionalHeaders;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置额外请求头。
|
||||
*
|
||||
* @param additionalHeaders 额外请求头
|
||||
*/
|
||||
public void setAdditionalHeaders(Map<String, String> additionalHeaders) {
|
||||
this.additionalHeaders = additionalHeaders == null ? new LinkedHashMap<>() : additionalHeaders;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取额外查询参数。
|
||||
*
|
||||
* @return 额外查询参数
|
||||
*/
|
||||
public Map<String, String> getAdditionalQueryParams() {
|
||||
return additionalQueryParams;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置额外查询参数。
|
||||
*
|
||||
* @param additionalQueryParams 额外查询参数
|
||||
*/
|
||||
public void setAdditionalQueryParams(Map<String, String> additionalQueryParams) {
|
||||
this.additionalQueryParams = additionalQueryParams == null ? new LinkedHashMap<>() : additionalQueryParams;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.easyagents.agent.runtime.model;
|
||||
|
||||
/**
|
||||
* 供应商无关的模型工厂标记接口。
|
||||
*
|
||||
* @param <T> 由适配器持有的具体模型类型
|
||||
*/
|
||||
public interface AgentModelFactory<T> {
|
||||
|
||||
/**
|
||||
* 创建a concrete model。
|
||||
*
|
||||
* @param modelSpec 模型声明
|
||||
* @param generationOptions 生成参数
|
||||
* @return 具体模型
|
||||
*/
|
||||
T create(AgentModelSpec modelSpec, AgentGenerationOptions generationOptions);
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.easyagents.agent.runtime.model;
|
||||
|
||||
/**
|
||||
* 智能体运行时支持的模型供应商类型。
|
||||
*/
|
||||
public enum AgentModelProviderType {
|
||||
OPENAI,
|
||||
OPENAI_COMPATIBLE,
|
||||
ANTHROPIC,
|
||||
GEMINI,
|
||||
OLLAMA,
|
||||
DEEPSEEK,
|
||||
GLM,
|
||||
MINIMAX,
|
||||
MOONSHOT,
|
||||
ARK,
|
||||
SILICONFLOW,
|
||||
DASHSCOPE,
|
||||
CUSTOM
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
package com.easyagents.agent.runtime.model;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 供应商无关的模型声明。
|
||||
*/
|
||||
public class AgentModelSpec {
|
||||
|
||||
private AgentModelProviderType providerType = AgentModelProviderType.OPENAI_COMPATIBLE;
|
||||
private String modelName;
|
||||
private String baseUrl;
|
||||
private String endpointPath;
|
||||
private String apiKey;
|
||||
private Map<String, Object> metadata = new LinkedHashMap<>();
|
||||
|
||||
/**
|
||||
* 获取供应商类型。
|
||||
*
|
||||
* @return 供应商类型
|
||||
*/
|
||||
public AgentModelProviderType getProviderType() {
|
||||
return providerType;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置供应商类型。
|
||||
*
|
||||
* @param providerType 供应商类型
|
||||
*/
|
||||
public void setProviderType(AgentModelProviderType providerType) {
|
||||
this.providerType = providerType == null ? AgentModelProviderType.OPENAI_COMPATIBLE : providerType;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取模型名称。
|
||||
*
|
||||
* @return 模型名称
|
||||
*/
|
||||
public String getModelName() {
|
||||
return modelName;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置模型名称。
|
||||
*
|
||||
* @param modelName 模型名称
|
||||
*/
|
||||
public void setModelName(String modelName) {
|
||||
this.modelName = modelName;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取基础 URL。
|
||||
*
|
||||
* @return 基础 URL
|
||||
*/
|
||||
public String getBaseUrl() {
|
||||
return baseUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置基础 URL。
|
||||
*
|
||||
* @param baseUrl 基础 URL
|
||||
*/
|
||||
public void setBaseUrl(String baseUrl) {
|
||||
this.baseUrl = baseUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取端点路径。
|
||||
*
|
||||
* @return 端点路径
|
||||
*/
|
||||
public String getEndpointPath() {
|
||||
return endpointPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置端点路径。
|
||||
*
|
||||
* @param endpointPath 端点路径
|
||||
*/
|
||||
public void setEndpointPath(String endpointPath) {
|
||||
this.endpointPath = endpointPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取API Key。
|
||||
*
|
||||
* @return API Key 值
|
||||
*/
|
||||
public String getApiKey() {
|
||||
return apiKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置API Key。
|
||||
*
|
||||
* @param apiKey API Key 值
|
||||
*/
|
||||
public void setApiKey(String apiKey) {
|
||||
this.apiKey = apiKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取元数据。
|
||||
*
|
||||
* @return 元数据
|
||||
*/
|
||||
public Map<String, Object> getMetadata() {
|
||||
return metadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置元数据。
|
||||
*
|
||||
* @param metadata 元数据
|
||||
*/
|
||||
public void setMetadata(Map<String, Object> metadata) {
|
||||
this.metadata = metadata == null ? new LinkedHashMap<>() : metadata;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.easyagents.agent.runtime.persistence;
|
||||
|
||||
import com.easyagents.agent.runtime.AgentRunRequest;
|
||||
import com.easyagents.agent.runtime.event.AgentRuntimeEvent;
|
||||
|
||||
/**
|
||||
* 记录业务会话事件。
|
||||
*/
|
||||
public interface AgentConversationRecorder {
|
||||
|
||||
/**
|
||||
* 记录一条运行时事件。
|
||||
*
|
||||
* @param request 运行请求
|
||||
* @param event 运行时事件
|
||||
*/
|
||||
void record(AgentRunRequest request, AgentRuntimeEvent event);
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
package com.easyagents.agent.runtime.persistence;
|
||||
|
||||
/**
|
||||
* 控制运行时状态持久化。
|
||||
*/
|
||||
public class AgentPersistencePolicy {
|
||||
|
||||
private boolean enabled;
|
||||
private boolean memoryManaged;
|
||||
private boolean toolkitManaged;
|
||||
private boolean planNotebookManaged;
|
||||
private boolean statefulToolsManaged;
|
||||
private boolean hitlManaged;
|
||||
private boolean sessionManaged;
|
||||
private boolean conversationRecordingEnabled;
|
||||
|
||||
/**
|
||||
* 创建禁用策略。
|
||||
*
|
||||
* @return 禁用策略
|
||||
*/
|
||||
public static AgentPersistencePolicy disabled() {
|
||||
return new AgentPersistencePolicy();
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建仅记忆持久化策略。
|
||||
*
|
||||
* @return 仅记忆持久化策略
|
||||
*/
|
||||
public static AgentPersistencePolicy memoryOnly() {
|
||||
AgentPersistencePolicy policy = new AgentPersistencePolicy();
|
||||
policy.setEnabled(true);
|
||||
policy.setMemoryManaged(true);
|
||||
policy.setSessionManaged(true);
|
||||
return policy;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回是否persistence is enabled。
|
||||
*
|
||||
* @return 启用标记
|
||||
*/
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置启用标记。
|
||||
*
|
||||
* @param enabled 启用标记
|
||||
*/
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回是否memory is managed。
|
||||
*
|
||||
* @return 记忆标记
|
||||
*/
|
||||
public boolean isMemoryManaged() {
|
||||
return memoryManaged;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置记忆标记。
|
||||
*
|
||||
* @param memoryManaged 记忆标记
|
||||
*/
|
||||
public void setMemoryManaged(boolean memoryManaged) {
|
||||
this.memoryManaged = memoryManaged;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回是否toolkit is managed。
|
||||
*
|
||||
* @return Toolkit 标记
|
||||
*/
|
||||
public boolean isToolkitManaged() {
|
||||
return toolkitManaged;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置Toolkit 标记。
|
||||
*
|
||||
* @param toolkitManaged Toolkit 标记
|
||||
*/
|
||||
public void setToolkitManaged(boolean toolkitManaged) {
|
||||
this.toolkitManaged = toolkitManaged;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回是否plan notebook is managed。
|
||||
*
|
||||
* @return 计划笔记标记
|
||||
*/
|
||||
public boolean isPlanNotebookManaged() {
|
||||
return planNotebookManaged;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置计划笔记标记。
|
||||
*
|
||||
* @param planNotebookManaged 计划笔记标记
|
||||
*/
|
||||
public void setPlanNotebookManaged(boolean planNotebookManaged) {
|
||||
this.planNotebookManaged = planNotebookManaged;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回是否stateful tools are managed。
|
||||
*
|
||||
* @return 有状态工具标记
|
||||
*/
|
||||
public boolean isStatefulToolsManaged() {
|
||||
return statefulToolsManaged;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置有状态工具标记。
|
||||
*
|
||||
* @param statefulToolsManaged 有状态工具标记
|
||||
*/
|
||||
public void setStatefulToolsManaged(boolean statefulToolsManaged) {
|
||||
this.statefulToolsManaged = statefulToolsManaged;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回是否HITL pending state is managed。
|
||||
*
|
||||
* @return HITL 标记
|
||||
*/
|
||||
public boolean isHitlManaged() {
|
||||
return hitlManaged;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置HITL 标记。
|
||||
*
|
||||
* @param hitlManaged HITL 标记
|
||||
*/
|
||||
public void setHitlManaged(boolean hitlManaged) {
|
||||
this.hitlManaged = hitlManaged;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回是否session storage is managed。
|
||||
*
|
||||
* @return 会话标记
|
||||
*/
|
||||
public boolean isSessionManaged() {
|
||||
return sessionManaged;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置会话标记。
|
||||
*
|
||||
* @param sessionManaged 会话标记
|
||||
*/
|
||||
public void setSessionManaged(boolean sessionManaged) {
|
||||
this.sessionManaged = sessionManaged;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回是否conversation events should be recorded。
|
||||
*
|
||||
* @return 会话记录标记
|
||||
*/
|
||||
public boolean isConversationRecordingEnabled() {
|
||||
return conversationRecordingEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置会话记录标记。
|
||||
*
|
||||
* @param conversationRecordingEnabled 会话记录标记
|
||||
*/
|
||||
public void setConversationRecordingEnabled(boolean conversationRecordingEnabled) {
|
||||
this.conversationRecordingEnabled = conversationRecordingEnabled;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
package com.easyagents.agent.runtime.persistence;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 可恢复状态的运行时检查点。
|
||||
*/
|
||||
public class AgentRuntimeCheckpoint {
|
||||
|
||||
private String checkpointId;
|
||||
private String sessionId;
|
||||
private String agentId;
|
||||
private Map<String, Object> state = new LinkedHashMap<>();
|
||||
private Instant createdAt = Instant.now();
|
||||
|
||||
/**
|
||||
* 获取检查点ID。
|
||||
*
|
||||
* @return 检查点ID
|
||||
*/
|
||||
public String getCheckpointId() {
|
||||
return checkpointId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置检查点ID。
|
||||
*
|
||||
* @param checkpointId 检查点ID
|
||||
*/
|
||||
public void setCheckpointId(String checkpointId) {
|
||||
this.checkpointId = checkpointId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取会话ID。
|
||||
*
|
||||
* @return 会话ID
|
||||
*/
|
||||
public String getSessionId() {
|
||||
return sessionId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置会话ID。
|
||||
*
|
||||
* @param sessionId 会话ID
|
||||
*/
|
||||
public void setSessionId(String sessionId) {
|
||||
this.sessionId = sessionId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取智能体ID。
|
||||
*
|
||||
* @return 智能体ID
|
||||
*/
|
||||
public String getAgentId() {
|
||||
return agentId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置智能体ID。
|
||||
*
|
||||
* @param agentId 智能体ID
|
||||
*/
|
||||
public void setAgentId(String agentId) {
|
||||
this.agentId = agentId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取状态载荷。
|
||||
*
|
||||
* @return 状态载荷
|
||||
*/
|
||||
public Map<String, Object> getState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置状态载荷。
|
||||
*
|
||||
* @param state 状态载荷
|
||||
*/
|
||||
public void setState(Map<String, Object> state) {
|
||||
this.state = state == null ? new LinkedHashMap<>() : state;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取创建时间。
|
||||
*
|
||||
* @return 创建时间
|
||||
*/
|
||||
public Instant getCreatedAt() {
|
||||
return createdAt;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置创建时间。
|
||||
*
|
||||
* @param createdAt 创建时间
|
||||
*/
|
||||
public void setCreatedAt(Instant createdAt) {
|
||||
this.createdAt = createdAt == null ? Instant.now() : createdAt;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
package com.easyagents.agent.runtime.persistence;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 运行时状态项。
|
||||
*
|
||||
* <p>MVP 中 value 是 JVM 对象态,不是稳定的传输格式,
|
||||
* 非内存 {@link AgentSessionStore} 实现必须显式完成序列化,
|
||||
* 再写入外部存储。</p>
|
||||
*/
|
||||
public class AgentRuntimeState {
|
||||
|
||||
private String name;
|
||||
private Object value;
|
||||
private Map<String, Object> metadata = new LinkedHashMap<>();
|
||||
|
||||
/**
|
||||
* 创建a 状态项。
|
||||
*
|
||||
* @param name 状态名称
|
||||
* @param value 状态值
|
||||
* @return 状态项
|
||||
*/
|
||||
public static AgentRuntimeState of(String name, Object value) {
|
||||
AgentRuntimeState state = new AgentRuntimeState();
|
||||
state.setName(name);
|
||||
state.setValue(value);
|
||||
return state;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取状态名称。
|
||||
*
|
||||
* @return 状态名称
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置状态名称。
|
||||
*
|
||||
* @param name 状态名称
|
||||
*/
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取状态值。
|
||||
*
|
||||
* @return 状态值
|
||||
*/
|
||||
public Object getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置状态值。
|
||||
*
|
||||
* @param value 状态值
|
||||
*/
|
||||
public void setValue(Object value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取元数据。
|
||||
*
|
||||
* @return 元数据
|
||||
*/
|
||||
public Map<String, Object> getMetadata() {
|
||||
return metadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置元数据。
|
||||
*
|
||||
* @param metadata 元数据
|
||||
*/
|
||||
public void setMetadata(Map<String, Object> metadata) {
|
||||
this.metadata = metadata == null ? new LinkedHashMap<>() : metadata;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package com.easyagents.agent.runtime.persistence;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 用于 AgentScope 类运行时会话状态的存储 SPI。
|
||||
*
|
||||
*/
|
||||
public interface AgentSessionStore {
|
||||
|
||||
/**
|
||||
* 保存one state value。
|
||||
*
|
||||
* @param sessionKey 会话键
|
||||
* @param name 状态名称
|
||||
* @param state 状态值
|
||||
*/
|
||||
void save(String sessionKey, String name, AgentRuntimeState state);
|
||||
|
||||
/**
|
||||
* 保存a state list。
|
||||
*
|
||||
* @param sessionKey 会话键
|
||||
* @param name 状态名称
|
||||
* @param states 状态列表
|
||||
*/
|
||||
void saveList(String sessionKey, String name, List<AgentRuntimeState> states);
|
||||
|
||||
/**
|
||||
* 获取one state value。
|
||||
*
|
||||
* @param sessionKey 会话键
|
||||
* @param name 状态名称
|
||||
* @return 可选状态
|
||||
*/
|
||||
Optional<AgentRuntimeState> get(String sessionKey, String name);
|
||||
|
||||
/**
|
||||
* 获取a state list。
|
||||
*
|
||||
* @param sessionKey 会话键
|
||||
* @param name 状态名称
|
||||
* @return 状态列表
|
||||
*/
|
||||
List<AgentRuntimeState> getList(String sessionKey, String name);
|
||||
|
||||
/**
|
||||
* 返回是否session key exists。
|
||||
*
|
||||
* @param sessionKey 会话键
|
||||
* @return 存在时为 true
|
||||
*/
|
||||
boolean exists(String sessionKey);
|
||||
|
||||
/**
|
||||
* 删除a session key。
|
||||
*
|
||||
* @param sessionKey 会话键
|
||||
*/
|
||||
void delete(String sessionKey);
|
||||
|
||||
/**
|
||||
* 列出会话键列表。
|
||||
*
|
||||
* @return 会话键列表
|
||||
*/
|
||||
Set<String> listSessionKeys();
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.easyagents.agent.runtime.persistence;
|
||||
|
||||
import com.easyagents.agent.runtime.AgentRuntimeException;
|
||||
|
||||
/**
|
||||
* 会话持久化异常。
|
||||
*/
|
||||
public class AgentSessionStoreException extends AgentRuntimeException {
|
||||
|
||||
/**
|
||||
* 创建会话持久化异常。
|
||||
*
|
||||
* @param message 错误消息
|
||||
*/
|
||||
public AgentSessionStoreException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建会话持久化异常。
|
||||
*
|
||||
* @param message 错误消息
|
||||
* @param cause 原始异常
|
||||
*/
|
||||
public AgentSessionStoreException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package com.easyagents.agent.runtime.persistence.json;
|
||||
|
||||
import com.easyagents.agent.runtime.persistence.AgentRuntimeState;
|
||||
import com.easyagents.agent.runtime.persistence.AgentSessionStoreException;
|
||||
import io.agentscope.core.state.State;
|
||||
import io.agentscope.core.util.JsonUtils;
|
||||
|
||||
/**
|
||||
* AgentScope 状态的 JSON 编解码器。
|
||||
*/
|
||||
public class AgentSessionStateCodec {
|
||||
|
||||
/**
|
||||
* 将状态对象编码为可持久化记录。
|
||||
*
|
||||
* @param state 状态对象
|
||||
* @return 可持久化记录
|
||||
*/
|
||||
public SerializedAgentRuntimeState encode(AgentRuntimeState state) {
|
||||
if (state == null || state.getValue() == null) {
|
||||
return null;
|
||||
}
|
||||
Object value = state.getValue();
|
||||
if (!(value instanceof State)) {
|
||||
throw new AgentSessionStoreException("Only AgentScope State values can be serialized: "
|
||||
+ value.getClass().getName());
|
||||
}
|
||||
SerializedAgentRuntimeState serialized = new SerializedAgentRuntimeState();
|
||||
serialized.setName(state.getName());
|
||||
serialized.setStateClassName(value.getClass().getName());
|
||||
serialized.setStateJson(JsonUtils.getJsonCodec().toJson(value));
|
||||
serialized.setMetadata(state.getMetadata());
|
||||
return serialized;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将持久化记录解码为运行时状态。
|
||||
*
|
||||
* @param serialized 可持久化记录
|
||||
* @return 运行时状态
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public AgentRuntimeState decode(SerializedAgentRuntimeState serialized) {
|
||||
if (serialized == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
Class<?> clazz = Class.forName(serialized.getStateClassName());
|
||||
if (!State.class.isAssignableFrom(clazz)) {
|
||||
throw new AgentSessionStoreException("Serialized state class is not AgentScope State: "
|
||||
+ serialized.getStateClassName());
|
||||
}
|
||||
State value = JsonUtils.getJsonCodec().fromJson(serialized.getStateJson(), (Class<? extends State>) clazz);
|
||||
AgentRuntimeState state = AgentRuntimeState.of(serialized.getName(), value);
|
||||
state.setMetadata(serialized.getMetadata());
|
||||
return state;
|
||||
} catch (ClassNotFoundException e) {
|
||||
throw new AgentSessionStoreException("Serialized state class not found: "
|
||||
+ serialized.getStateClassName(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package com.easyagents.agent.runtime.persistence.json;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* JSON 会话状态的底层键值存储后端。
|
||||
*/
|
||||
public interface AgentSessionStoreBackend {
|
||||
|
||||
/**
|
||||
* 保存单个状态记录。
|
||||
*
|
||||
* @param sessionKey 会话键
|
||||
* @param name 状态名称
|
||||
* @param state 状态记录
|
||||
*/
|
||||
void save(String sessionKey, String name, SerializedAgentRuntimeState state);
|
||||
|
||||
/**
|
||||
* 保存状态记录列表。
|
||||
*
|
||||
* @param sessionKey 会话键
|
||||
* @param name 状态名称
|
||||
* @param states 状态记录列表
|
||||
*/
|
||||
void saveList(String sessionKey, String name, List<SerializedAgentRuntimeState> states);
|
||||
|
||||
/**
|
||||
* 获取单个状态记录。
|
||||
*
|
||||
* @param sessionKey 会话键
|
||||
* @param name 状态名称
|
||||
* @return 状态记录
|
||||
*/
|
||||
Optional<SerializedAgentRuntimeState> get(String sessionKey, String name);
|
||||
|
||||
/**
|
||||
* 获取状态记录列表。
|
||||
*
|
||||
* @param sessionKey 会话键
|
||||
* @param name 状态名称
|
||||
* @return 状态记录列表
|
||||
*/
|
||||
List<SerializedAgentRuntimeState> getList(String sessionKey, String name);
|
||||
|
||||
/**
|
||||
* 判断会话是否存在。
|
||||
*
|
||||
* @param sessionKey 会话键
|
||||
* @return 存在时为 true
|
||||
*/
|
||||
boolean exists(String sessionKey);
|
||||
|
||||
/**
|
||||
* 删除会话。
|
||||
*
|
||||
* @param sessionKey 会话键
|
||||
*/
|
||||
void delete(String sessionKey);
|
||||
|
||||
/**
|
||||
* 列出全部会话键。
|
||||
*
|
||||
* @return 会话键集合
|
||||
*/
|
||||
Set<String> listSessionKeys();
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
package com.easyagents.agent.runtime.persistence.json;
|
||||
|
||||
import com.easyagents.agent.runtime.persistence.AgentSessionStoreException;
|
||||
import io.agentscope.core.util.JsonUtils;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.util.*;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* 基于本地文件的 JSON 会话状态后端。
|
||||
*/
|
||||
public class FileAgentSessionStoreBackend implements AgentSessionStoreBackend {
|
||||
|
||||
private static final Pattern SAFE_NAME = Pattern.compile("[A-Za-z0-9._-]+");
|
||||
private final Path rootDirectory;
|
||||
|
||||
/**
|
||||
* 创建文件后端。
|
||||
*
|
||||
* @param rootDirectory 根目录
|
||||
*/
|
||||
public FileAgentSessionStoreBackend(Path rootDirectory) {
|
||||
if (rootDirectory == null) {
|
||||
throw new AgentSessionStoreException("Agent session root directory is required.");
|
||||
}
|
||||
this.rootDirectory = rootDirectory;
|
||||
try {
|
||||
Files.createDirectories(rootDirectory);
|
||||
} catch (IOException e) {
|
||||
throw new AgentSessionStoreException("Failed to create agent session root directory: " + rootDirectory, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save(String sessionKey, String name, SerializedAgentRuntimeState state) {
|
||||
if (state == null) {
|
||||
return;
|
||||
}
|
||||
Path path = statePath(sessionKey, name);
|
||||
createParent(path);
|
||||
try {
|
||||
Files.writeString(path, JsonUtils.getJsonCodec().toPrettyJson(state), StandardCharsets.UTF_8,
|
||||
StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.WRITE);
|
||||
} catch (IOException e) {
|
||||
throw new AgentSessionStoreException("Failed to save agent session state: " + sessionKey + "/" + name, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveList(String sessionKey, String name, List<SerializedAgentRuntimeState> states) {
|
||||
Path path = listPath(sessionKey, name);
|
||||
createParent(path);
|
||||
try (BufferedWriter writer = Files.newBufferedWriter(path, StandardCharsets.UTF_8,
|
||||
StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.WRITE)) {
|
||||
if (states == null) {
|
||||
return;
|
||||
}
|
||||
for (SerializedAgentRuntimeState state : states) {
|
||||
writer.write(JsonUtils.getJsonCodec().toJson(state));
|
||||
writer.newLine();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new AgentSessionStoreException("Failed to save agent session state list: " + sessionKey + "/" + name, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<SerializedAgentRuntimeState> get(String sessionKey, String name) {
|
||||
Path path = statePath(sessionKey, name);
|
||||
if (!Files.exists(path)) {
|
||||
return Optional.empty();
|
||||
}
|
||||
try {
|
||||
String json = Files.readString(path, StandardCharsets.UTF_8);
|
||||
return Optional.of(JsonUtils.getJsonCodec().fromJson(json, SerializedAgentRuntimeState.class));
|
||||
} catch (IOException e) {
|
||||
throw new AgentSessionStoreException("Failed to load agent session state: " + sessionKey + "/" + name, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SerializedAgentRuntimeState> getList(String sessionKey, String name) {
|
||||
Path path = listPath(sessionKey, name);
|
||||
if (!Files.exists(path)) {
|
||||
return List.of();
|
||||
}
|
||||
List<SerializedAgentRuntimeState> states = new ArrayList<>();
|
||||
try (BufferedReader reader = Files.newBufferedReader(path, StandardCharsets.UTF_8)) {
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
if (!line.isBlank()) {
|
||||
states.add(JsonUtils.getJsonCodec().fromJson(line, SerializedAgentRuntimeState.class));
|
||||
}
|
||||
}
|
||||
return states;
|
||||
} catch (IOException e) {
|
||||
throw new AgentSessionStoreException("Failed to load agent session state list: " + sessionKey + "/" + name, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean exists(String sessionKey) {
|
||||
return Files.isDirectory(sessionDirectory(sessionKey));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete(String sessionKey) {
|
||||
Path directory = sessionDirectory(sessionKey);
|
||||
if (!Files.exists(directory)) {
|
||||
return;
|
||||
}
|
||||
try (Stream<Path> paths = Files.walk(directory)) {
|
||||
List<Path> ordered = paths.sorted(Comparator.reverseOrder()).collect(Collectors.toList());
|
||||
for (Path path : ordered) {
|
||||
Files.deleteIfExists(path);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new AgentSessionStoreException("Failed to delete agent session: " + sessionKey, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> listSessionKeys() {
|
||||
if (!Files.exists(rootDirectory)) {
|
||||
return Set.of();
|
||||
}
|
||||
try (Stream<Path> paths = Files.list(rootDirectory)) {
|
||||
return paths.filter(Files::isDirectory)
|
||||
.map(path -> path.getFileName().toString())
|
||||
.collect(Collectors.toCollection(LinkedHashSet::new));
|
||||
} catch (IOException e) {
|
||||
throw new AgentSessionStoreException("Failed to list agent sessions.", e);
|
||||
}
|
||||
}
|
||||
|
||||
private Path statePath(String sessionKey, String name) {
|
||||
return sessionDirectory(sessionKey).resolve(safeName(name) + ".json");
|
||||
}
|
||||
|
||||
private Path listPath(String sessionKey, String name) {
|
||||
return sessionDirectory(sessionKey).resolve(safeName(name) + ".jsonl");
|
||||
}
|
||||
|
||||
private Path sessionDirectory(String sessionKey) {
|
||||
return rootDirectory.resolve(safeName(sessionKey));
|
||||
}
|
||||
|
||||
private String safeName(String value) {
|
||||
if (value == null || value.isBlank() || !SAFE_NAME.matcher(value).matches()
|
||||
|| value.contains("..")) {
|
||||
throw new AgentSessionStoreException("Unsafe agent session storage key: " + value);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
private void createParent(Path path) {
|
||||
try {
|
||||
Files.createDirectories(path.getParent());
|
||||
} catch (IOException e) {
|
||||
throw new AgentSessionStoreException("Failed to create agent session directory: " + path.getParent(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
package com.easyagents.agent.runtime.persistence.json;
|
||||
|
||||
import com.easyagents.agent.runtime.persistence.AgentRuntimeState;
|
||||
import com.easyagents.agent.runtime.persistence.AgentSessionStore;
|
||||
import com.easyagents.agent.runtime.persistence.AgentSessionStoreException;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 基于 JSON 记录的默认非内存会话存储。
|
||||
*/
|
||||
public class JsonAgentSessionStore implements AgentSessionStore {
|
||||
|
||||
private final AgentSessionStoreBackend backend;
|
||||
private final AgentSessionStateCodec codec;
|
||||
|
||||
/**
|
||||
* 创建文件型 JSON 会话存储。
|
||||
*
|
||||
* @param rootDirectory 根目录
|
||||
*/
|
||||
public JsonAgentSessionStore(Path rootDirectory) {
|
||||
this(new FileAgentSessionStoreBackend(rootDirectory), new AgentSessionStateCodec());
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建可替换后端的 JSON 会话存储。
|
||||
*
|
||||
* @param backend 底层存储后端
|
||||
*/
|
||||
public JsonAgentSessionStore(AgentSessionStoreBackend backend) {
|
||||
this(backend, new AgentSessionStateCodec());
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建可替换后端和编解码器的 JSON 会话存储。
|
||||
*
|
||||
* @param backend 底层存储后端
|
||||
* @param codec 状态编解码器
|
||||
*/
|
||||
public JsonAgentSessionStore(AgentSessionStoreBackend backend, AgentSessionStateCodec codec) {
|
||||
if (backend == null) {
|
||||
throw new AgentSessionStoreException("Agent session store backend is required.");
|
||||
}
|
||||
this.backend = backend;
|
||||
this.codec = codec == null ? new AgentSessionStateCodec() : codec;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save(String sessionKey, String name, AgentRuntimeState state) {
|
||||
SerializedAgentRuntimeState serialized = codec.encode(state);
|
||||
if (serialized != null) {
|
||||
backend.save(sessionKey, name, serialized);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveList(String sessionKey, String name, List<AgentRuntimeState> states) {
|
||||
List<SerializedAgentRuntimeState> serialized = new ArrayList<>();
|
||||
if (states != null) {
|
||||
for (AgentRuntimeState state : states) {
|
||||
SerializedAgentRuntimeState item = codec.encode(state);
|
||||
if (item != null) {
|
||||
serialized.add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
backend.saveList(sessionKey, name, serialized);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<AgentRuntimeState> get(String sessionKey, String name) {
|
||||
return backend.get(sessionKey, name).map(codec::decode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<AgentRuntimeState> getList(String sessionKey, String name) {
|
||||
List<AgentRuntimeState> states = new ArrayList<>();
|
||||
for (SerializedAgentRuntimeState item : backend.getList(sessionKey, name)) {
|
||||
AgentRuntimeState state = codec.decode(item);
|
||||
if (state != null) {
|
||||
states.add(state);
|
||||
}
|
||||
}
|
||||
return states;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean exists(String sessionKey) {
|
||||
return backend.exists(sessionKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete(String sessionKey) {
|
||||
backend.delete(sessionKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> listSessionKeys() {
|
||||
return backend.listSessionKeys();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
package com.easyagents.agent.runtime.persistence.json;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 可跨进程持久化的 AgentScope 状态记录。
|
||||
*/
|
||||
public class SerializedAgentRuntimeState {
|
||||
|
||||
private String name;
|
||||
private String stateClassName;
|
||||
private String stateJson;
|
||||
private Map<String, Object> metadata = new LinkedHashMap<>();
|
||||
private Instant createdAt = Instant.now();
|
||||
|
||||
/**
|
||||
* 获取状态名称。
|
||||
*
|
||||
* @return 状态名称
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置状态名称。
|
||||
*
|
||||
* @param name 状态名称
|
||||
*/
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取状态类名。
|
||||
*
|
||||
* @return 状态类名
|
||||
*/
|
||||
public String getStateClassName() {
|
||||
return stateClassName;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置状态类名。
|
||||
*
|
||||
* @param stateClassName 状态类名
|
||||
*/
|
||||
public void setStateClassName(String stateClassName) {
|
||||
this.stateClassName = stateClassName;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取状态 JSON。
|
||||
*
|
||||
* @return 状态 JSON
|
||||
*/
|
||||
public String getStateJson() {
|
||||
return stateJson;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置状态 JSON。
|
||||
*
|
||||
* @param stateJson 状态 JSON
|
||||
*/
|
||||
public void setStateJson(String stateJson) {
|
||||
this.stateJson = stateJson;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取元数据。
|
||||
*
|
||||
* @return 元数据
|
||||
*/
|
||||
public Map<String, Object> getMetadata() {
|
||||
return metadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置元数据。
|
||||
*
|
||||
* @param metadata 元数据
|
||||
*/
|
||||
public void setMetadata(Map<String, Object> metadata) {
|
||||
this.metadata = metadata == null ? new LinkedHashMap<>() : metadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取创建时间。
|
||||
*
|
||||
* @return 创建时间
|
||||
*/
|
||||
public Instant getCreatedAt() {
|
||||
return createdAt;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置创建时间。
|
||||
*
|
||||
* @param createdAt 创建时间
|
||||
*/
|
||||
public void setCreatedAt(Instant createdAt) {
|
||||
this.createdAt = createdAt == null ? Instant.now() : createdAt;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package com.easyagents.agent.runtime.persistence.memory;
|
||||
|
||||
import com.easyagents.agent.runtime.persistence.AgentRuntimeState;
|
||||
import com.easyagents.agent.runtime.persistence.AgentSessionStore;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* 用于测试和单节点 MVP 的内存会话存储。
|
||||
*/
|
||||
public class InMemoryAgentSessionStore implements AgentSessionStore {
|
||||
|
||||
private final Map<String, Map<String, List<AgentRuntimeState>>> states = new ConcurrentHashMap<>();
|
||||
|
||||
@Override
|
||||
public void save(String sessionKey, String name, AgentRuntimeState state) {
|
||||
if (sessionKey == null || name == null || state == null) {
|
||||
return;
|
||||
}
|
||||
saveList(sessionKey, name, List.of(state));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveList(String sessionKey, String name, List<AgentRuntimeState> states) {
|
||||
if (sessionKey == null || name == null) {
|
||||
return;
|
||||
}
|
||||
this.states.computeIfAbsent(sessionKey, key -> new ConcurrentHashMap<>())
|
||||
.put(name, states == null ? new ArrayList<>() : new ArrayList<>(states));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<AgentRuntimeState> get(String sessionKey, String name) {
|
||||
List<AgentRuntimeState> list = getList(sessionKey, name);
|
||||
return list.isEmpty() ? Optional.empty() : Optional.ofNullable(list.get(0));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<AgentRuntimeState> getList(String sessionKey, String name) {
|
||||
Map<String, List<AgentRuntimeState>> byName = states.getOrDefault(sessionKey, new LinkedHashMap<>());
|
||||
return new ArrayList<>(byName.getOrDefault(name, new ArrayList<>()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean exists(String sessionKey) {
|
||||
return states.containsKey(sessionKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete(String sessionKey) {
|
||||
states.remove(sessionKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> listSessionKeys() {
|
||||
return new LinkedHashSet<>(states.keySet());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.easyagents.agent.runtime.persistence.noop;
|
||||
|
||||
import com.easyagents.agent.runtime.AgentRunRequest;
|
||||
import com.easyagents.agent.runtime.event.AgentRuntimeEvent;
|
||||
import com.easyagents.agent.runtime.persistence.AgentConversationRecorder;
|
||||
|
||||
/**
|
||||
* 空操作会话记录器。
|
||||
*/
|
||||
public enum NoopAgentConversationRecorder implements AgentConversationRecorder {
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public void record(AgentRunRequest request, AgentRuntimeEvent event) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package com.easyagents.agent.runtime.persistence.noop;
|
||||
|
||||
import com.easyagents.agent.runtime.persistence.AgentRuntimeState;
|
||||
import com.easyagents.agent.runtime.persistence.AgentSessionStore;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 空操作会话存储。
|
||||
*/
|
||||
public enum NoopAgentSessionStore implements AgentSessionStore {
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public void save(String sessionKey, String name, AgentRuntimeState state) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveList(String sessionKey, String name, List<AgentRuntimeState> states) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<AgentRuntimeState> get(String sessionKey, String name) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<AgentRuntimeState> getList(String sessionKey, String name) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean exists(String sessionKey) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete(String sessionKey) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> listSessionKeys() {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package com.easyagents.agent.runtime.skill;
|
||||
|
||||
/**
|
||||
* Skill 与工具的运行时绑定关系。
|
||||
*/
|
||||
public class AgentSkillBinding {
|
||||
|
||||
private final String skillId;
|
||||
private final String skillName;
|
||||
private final String skillBoxId;
|
||||
|
||||
/**
|
||||
* 创建 Skill 绑定关系。
|
||||
*
|
||||
* @param skillId Skill ID
|
||||
* @param skillName Skill 名称
|
||||
* @param skillBoxId SkillBox ID
|
||||
*/
|
||||
public AgentSkillBinding(String skillId, String skillName, String skillBoxId) {
|
||||
this.skillId = skillId;
|
||||
this.skillName = skillName;
|
||||
this.skillBoxId = skillBoxId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 Skill ID。
|
||||
*
|
||||
* @return Skill ID
|
||||
*/
|
||||
public String getSkillId() {
|
||||
return skillId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 Skill 名称。
|
||||
*
|
||||
* @return Skill 名称
|
||||
*/
|
||||
public String getSkillName() {
|
||||
return skillName;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 SkillBox ID。
|
||||
*
|
||||
* @return SkillBox ID
|
||||
*/
|
||||
public String getSkillBoxId() {
|
||||
return skillBoxId;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,147 @@
|
||||
package com.easyagents.agent.runtime.skill;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 声明式 SkillBox 配置。
|
||||
*/
|
||||
public class AgentSkillBoxSpec {
|
||||
|
||||
private String skillBoxId;
|
||||
private List<AgentSkillSpec> skills = new ArrayList<>();
|
||||
private Map<String, List<String>> toolBindings = new LinkedHashMap<>();
|
||||
private List<String> enabledToolNames = new ArrayList<>();
|
||||
private List<String> disabledToolNames = new ArrayList<>();
|
||||
private Map<String, Map<String, Object>> presetParameters = new LinkedHashMap<>();
|
||||
private boolean exposeAllSkillMetadata;
|
||||
|
||||
/**
|
||||
* 获取SkillBox ID。
|
||||
*
|
||||
* @return SkillBox ID 标识
|
||||
*/
|
||||
public String getSkillBoxId() {
|
||||
return skillBoxId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置SkillBox ID。
|
||||
*
|
||||
* @param skillBoxId SkillBox ID 标识
|
||||
*/
|
||||
public void setSkillBoxId(String skillBoxId) {
|
||||
this.skillBoxId = skillBoxId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取Skill 列表。
|
||||
*
|
||||
* @return Skill 列表
|
||||
*/
|
||||
public List<AgentSkillSpec> getSkills() {
|
||||
return skills;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置Skill 列表。
|
||||
*
|
||||
* @param skills Skill 列表
|
||||
*/
|
||||
public void setSkills(List<AgentSkillSpec> skills) {
|
||||
this.skills = skills == null ? new ArrayList<>() : new ArrayList<>(skills);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取工具绑定。
|
||||
*
|
||||
* @return 工具绑定
|
||||
*/
|
||||
public Map<String, List<String>> getToolBindings() {
|
||||
return toolBindings;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置工具绑定。
|
||||
*
|
||||
* @param toolBindings 工具绑定
|
||||
*/
|
||||
public void setToolBindings(Map<String, List<String>> toolBindings) {
|
||||
this.toolBindings = toolBindings == null ? new LinkedHashMap<>() : toolBindings;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取启用的工具名称。
|
||||
*
|
||||
* @return 启用的工具名称
|
||||
*/
|
||||
public List<String> getEnabledToolNames() {
|
||||
return enabledToolNames;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置启用的工具名称。
|
||||
*
|
||||
* @param enabledToolNames 启用的工具名称
|
||||
*/
|
||||
public void setEnabledToolNames(List<String> enabledToolNames) {
|
||||
this.enabledToolNames = enabledToolNames == null ? new ArrayList<>() : new ArrayList<>(enabledToolNames);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取禁用的工具名称。
|
||||
*
|
||||
* @return 禁用的工具名称
|
||||
*/
|
||||
public List<String> getDisabledToolNames() {
|
||||
return disabledToolNames;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置禁用的工具名称。
|
||||
*
|
||||
* @param disabledToolNames 禁用的工具名称
|
||||
*/
|
||||
public void setDisabledToolNames(List<String> disabledToolNames) {
|
||||
this.disabledToolNames = disabledToolNames == null ? new ArrayList<>() : new ArrayList<>(disabledToolNames);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取预设参数。
|
||||
*
|
||||
* @return 预设参数
|
||||
*/
|
||||
public Map<String, Map<String, Object>> getPresetParameters() {
|
||||
return presetParameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置预设参数。
|
||||
*
|
||||
* @param presetParameters 预设参数
|
||||
*/
|
||||
public void setPresetParameters(Map<String, Map<String, Object>> presetParameters) {
|
||||
this.presetParameters = presetParameters == null ? new LinkedHashMap<>() : presetParameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回是否all skill metadata is exposed。
|
||||
*
|
||||
* @return 所有元数据暴露时为 true
|
||||
*/
|
||||
public boolean isExposeAllSkillMetadata() {
|
||||
return exposeAllSkillMetadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置元数据暴露开关。
|
||||
*
|
||||
* @param exposeAllSkillMetadata 元数据暴露开关
|
||||
*/
|
||||
public void setExposeAllSkillMetadata(boolean exposeAllSkillMetadata) {
|
||||
this.exposeAllSkillMetadata = exposeAllSkillMetadata;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.easyagents.agent.runtime.skill;
|
||||
|
||||
/**
|
||||
* 将声明式 Skill 编译为适配器持有的 Skill 类型。
|
||||
*
|
||||
* @param <T> 编译后的 Skill 类型
|
||||
*/
|
||||
public interface AgentSkillCompiler<T> {
|
||||
|
||||
/**
|
||||
* 编译单个 Skill。
|
||||
*
|
||||
* @param skillSpec Skill 声明
|
||||
* @return 编译后的 Skill
|
||||
*/
|
||||
T compile(AgentSkillSpec skillSpec);
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
package com.easyagents.agent.runtime.skill;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Skill 加载工具调用记录。
|
||||
*/
|
||||
public class AgentSkillLoadCall {
|
||||
|
||||
private final String toolCallId;
|
||||
private final String skillId;
|
||||
private final String skillName;
|
||||
private final String skillBoxId;
|
||||
private final String path;
|
||||
private final Map<String, Object> input;
|
||||
|
||||
/**
|
||||
* 创建 Skill 加载工具调用记录。
|
||||
*
|
||||
* @param toolCallId 工具调用 ID
|
||||
* @param skillId Skill ID
|
||||
* @param skillName Skill 名称
|
||||
* @param skillBoxId SkillBox ID
|
||||
* @param path 资源路径
|
||||
* @param input 工具输入
|
||||
*/
|
||||
public AgentSkillLoadCall(String toolCallId,
|
||||
String skillId,
|
||||
String skillName,
|
||||
String skillBoxId,
|
||||
String path,
|
||||
Map<String, Object> input) {
|
||||
this.toolCallId = toolCallId;
|
||||
this.skillId = skillId;
|
||||
this.skillName = skillName;
|
||||
this.skillBoxId = skillBoxId;
|
||||
this.path = path;
|
||||
this.input = input == null ? new LinkedHashMap<>() : new LinkedHashMap<>(input);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取工具调用 ID。
|
||||
*
|
||||
* @return 工具调用 ID
|
||||
*/
|
||||
public String getToolCallId() {
|
||||
return toolCallId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 Skill ID。
|
||||
*
|
||||
* @return Skill ID
|
||||
*/
|
||||
public String getSkillId() {
|
||||
return skillId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 Skill 名称。
|
||||
*
|
||||
* @return Skill 名称
|
||||
*/
|
||||
public String getSkillName() {
|
||||
return skillName;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 SkillBox ID。
|
||||
*
|
||||
* @return SkillBox ID
|
||||
*/
|
||||
public String getSkillBoxId() {
|
||||
return skillBoxId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取资源路径。
|
||||
*
|
||||
* @return 资源路径
|
||||
*/
|
||||
public String getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取工具输入。
|
||||
*
|
||||
* @return 工具输入
|
||||
*/
|
||||
public Map<String, Object> getInput() {
|
||||
return input;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
package com.easyagents.agent.runtime.skill;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* Skill 运行时上下文。
|
||||
*/
|
||||
public class AgentSkillRuntimeContext {
|
||||
|
||||
/**
|
||||
* AgentScope SkillBox 内置的 Skill 加载工具名称。
|
||||
*/
|
||||
public static final String LOAD_SKILL_TOOL_NAME = "load_skill_through_path";
|
||||
|
||||
private final Map<String, AgentSkillBinding> skillBindings;
|
||||
private final Map<String, AgentSkillBinding> toolBindings;
|
||||
private final Map<String, AgentSkillLoadCall> pendingLoadCalls = new ConcurrentHashMap<>();
|
||||
private final Map<String, Boolean> emittedLoadCalls = new ConcurrentHashMap<>();
|
||||
private final Map<String, Boolean> activeSkills = new ConcurrentHashMap<>();
|
||||
|
||||
private AgentSkillRuntimeContext(Map<String, AgentSkillBinding> skillBindings,
|
||||
Map<String, AgentSkillBinding> toolBindings) {
|
||||
this.skillBindings = skillBindings;
|
||||
this.toolBindings = toolBindings;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 SkillBox 配置创建运行时上下文。
|
||||
*
|
||||
* @param spec SkillBox 配置
|
||||
* @return Skill 运行时上下文
|
||||
*/
|
||||
public static AgentSkillRuntimeContext from(AgentSkillBoxSpec spec) {
|
||||
if (spec == null) {
|
||||
return new AgentSkillRuntimeContext(new LinkedHashMap<>(), new LinkedHashMap<>());
|
||||
}
|
||||
Map<String, AgentSkillBinding> skillBindings = new LinkedHashMap<>();
|
||||
for (AgentSkillSpec skillSpec : spec.getSkills()) {
|
||||
if (skillSpec.getSkillId() == null || skillSpec.getSkillId().isBlank()) {
|
||||
continue;
|
||||
}
|
||||
skillBindings.put(skillSpec.getSkillId(),
|
||||
new AgentSkillBinding(skillSpec.getSkillId(), skillSpec.getName(), spec.getSkillBoxId()));
|
||||
}
|
||||
Map<String, AgentSkillBinding> toolBindings = new LinkedHashMap<>();
|
||||
for (Map.Entry<String, List<String>> entry : spec.getToolBindings().entrySet()) {
|
||||
AgentSkillBinding binding = skillBindings.get(entry.getKey());
|
||||
if (binding == null || entry.getValue() == null) {
|
||||
continue;
|
||||
}
|
||||
for (String toolName : entry.getValue()) {
|
||||
if (toolName != null && !toolName.isBlank()) {
|
||||
toolBindings.put(toolName, binding);
|
||||
}
|
||||
}
|
||||
}
|
||||
return new AgentSkillRuntimeContext(skillBindings, toolBindings);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否为 Skill 加载工具。
|
||||
*
|
||||
* @param toolName 工具名称
|
||||
* @return 是 Skill 加载工具时为 true
|
||||
*/
|
||||
public boolean isSkillLoadTool(String toolName) {
|
||||
return LOAD_SKILL_TOOL_NAME.equals(toolName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据 Skill ID 获取绑定关系。
|
||||
*
|
||||
* @param skillId Skill ID
|
||||
* @return 绑定关系
|
||||
*/
|
||||
public AgentSkillBinding getSkillBinding(String skillId) {
|
||||
return skillBindings.get(skillId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据工具名称获取 Skill 绑定关系。
|
||||
*
|
||||
* @param toolName 工具名称
|
||||
* @return 绑定关系
|
||||
*/
|
||||
public AgentSkillBinding getToolBinding(String toolName) {
|
||||
return toolBindings.get(toolName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断工具是否绑定到 Skill。
|
||||
*
|
||||
* @param toolName 工具名称
|
||||
* @return 绑定到 Skill 时为 true
|
||||
*/
|
||||
public boolean isSkillBoundTool(String toolName) {
|
||||
return toolBindings.containsKey(toolName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 激活指定 Skill。
|
||||
*
|
||||
* @param skillId Skill ID
|
||||
*/
|
||||
public void activateSkill(String skillId) {
|
||||
if (skillId != null && !skillId.isBlank()) {
|
||||
activeSkills.put(skillId, true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断 Skill 是否已激活。
|
||||
*
|
||||
* @param skillId Skill ID
|
||||
* @return 已激活时为 true
|
||||
*/
|
||||
public boolean isSkillActive(String skillId) {
|
||||
return Boolean.TRUE.equals(activeSkills.get(skillId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取已激活 Skill 的工具绑定。
|
||||
*
|
||||
* @param toolName 工具名称
|
||||
* @return 已激活 Skill 的绑定关系
|
||||
*/
|
||||
public AgentSkillBinding getActiveToolBinding(String toolName) {
|
||||
AgentSkillBinding binding = getToolBinding(toolName);
|
||||
if (binding == null || !isSkillActive(binding.getSkillId())) {
|
||||
return null;
|
||||
}
|
||||
return binding;
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录一次 Skill 加载工具调用。
|
||||
*
|
||||
* @param toolCallId 工具调用 ID
|
||||
* @param input 工具输入
|
||||
* @return Skill 加载调用
|
||||
*/
|
||||
public AgentSkillLoadCall rememberLoadCall(String toolCallId, Map<String, Object> input) {
|
||||
if (toolCallId == null || input == null) {
|
||||
return null;
|
||||
}
|
||||
String skillId = stringValue(input.get("skillId"));
|
||||
String path = stringValue(input.get("path"));
|
||||
AgentSkillBinding binding = getSkillBinding(skillId);
|
||||
AgentSkillLoadCall call = new AgentSkillLoadCall(toolCallId, skillId,
|
||||
binding == null ? null : binding.getSkillName(),
|
||||
binding == null ? null : binding.getSkillBoxId(), path, input);
|
||||
pendingLoadCalls.put(toolCallId, call);
|
||||
return call;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断 Skill 加载调用事件是否已经发射。
|
||||
*
|
||||
* @param toolCallId 工具调用 ID
|
||||
* @return 首次发射时为 true
|
||||
*/
|
||||
public boolean markLoadCallEmitted(String toolCallId) {
|
||||
if (toolCallId == null) {
|
||||
return false;
|
||||
}
|
||||
return emittedLoadCalls.putIfAbsent(toolCallId, true) == null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取并移除一次 Skill 加载工具调用。
|
||||
*
|
||||
* @param toolCallId 工具调用 ID
|
||||
* @return Skill 加载调用
|
||||
*/
|
||||
public AgentSkillLoadCall removeLoadCall(String toolCallId) {
|
||||
if (toolCallId == null) {
|
||||
return null;
|
||||
}
|
||||
emittedLoadCalls.remove(toolCallId);
|
||||
return pendingLoadCalls.remove(toolCallId);
|
||||
}
|
||||
|
||||
private static String stringValue(Object value) {
|
||||
return value == null ? null : String.valueOf(value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
package com.easyagents.agent.runtime.skill;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 声明式 Skill 配置。
|
||||
*/
|
||||
public class AgentSkillSpec {
|
||||
|
||||
private String skillId;
|
||||
private String name;
|
||||
private String description;
|
||||
private String skillContent;
|
||||
private Map<String, String> resources = new LinkedHashMap<>();
|
||||
private String source;
|
||||
private Map<String, Object> metadata = new LinkedHashMap<>();
|
||||
|
||||
/**
|
||||
* 获取Skill ID。
|
||||
*
|
||||
* @return Skill ID 标识
|
||||
*/
|
||||
public String getSkillId() {
|
||||
return skillId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置Skill ID。
|
||||
*
|
||||
* @param skillId Skill ID 标识
|
||||
*/
|
||||
public void setSkillId(String skillId) {
|
||||
this.skillId = skillId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取Skill 名称。
|
||||
*
|
||||
* @return Skill 名称
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置Skill 名称。
|
||||
*
|
||||
* @param name Skill 名称
|
||||
*/
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取描述。
|
||||
*
|
||||
* @return 描述
|
||||
*/
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置描述。
|
||||
*
|
||||
* @param description 描述
|
||||
*/
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取Skill 内容。
|
||||
*
|
||||
* @return Skill 内容
|
||||
*/
|
||||
public String getSkillContent() {
|
||||
return skillContent;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置Skill 内容。
|
||||
*
|
||||
* @param skillContent Skill 内容
|
||||
*/
|
||||
public void setSkillContent(String skillContent) {
|
||||
this.skillContent = skillContent;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取资源映射。
|
||||
*
|
||||
* @return 资源映射
|
||||
*/
|
||||
public Map<String, String> getResources() {
|
||||
return resources;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置资源映射。
|
||||
*
|
||||
* @param resources 资源映射
|
||||
*/
|
||||
public void setResources(Map<String, String> resources) {
|
||||
this.resources = resources == null ? new LinkedHashMap<>() : new LinkedHashMap<>(resources);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取来源。
|
||||
*
|
||||
* @return 来源
|
||||
*/
|
||||
public String getSource() {
|
||||
return source;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置来源。
|
||||
*
|
||||
* @param source 来源
|
||||
*/
|
||||
public void setSource(String source) {
|
||||
this.source = source;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取元数据。
|
||||
*
|
||||
* @return 元数据
|
||||
*/
|
||||
public Map<String, Object> getMetadata() {
|
||||
return metadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置元数据。
|
||||
*
|
||||
* @param metadata 元数据
|
||||
*/
|
||||
public void setMetadata(Map<String, Object> metadata) {
|
||||
this.metadata = metadata == null ? new LinkedHashMap<>() : new LinkedHashMap<>(metadata);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.easyagents.agent.runtime.tool;
|
||||
|
||||
/**
|
||||
* 动态工具分类。
|
||||
*/
|
||||
public enum AgentToolCategory {
|
||||
WORKFLOW,
|
||||
PLUGIN,
|
||||
MCP,
|
||||
KNOWLEDGE,
|
||||
CUSTOM
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
package com.easyagents.agent.runtime.tool;
|
||||
|
||||
import com.easyagents.agent.runtime.AgentRuntimeContext;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 传递给动态工具调用的上下文。
|
||||
*/
|
||||
public class AgentToolContext {
|
||||
|
||||
private String requestId;
|
||||
private String traceId;
|
||||
private String sessionId;
|
||||
private String agentId;
|
||||
private String toolCallId;
|
||||
private AgentRuntimeContext runtimeContext = new AgentRuntimeContext();
|
||||
private Map<String, Object> metadata = new LinkedHashMap<>();
|
||||
|
||||
/**
|
||||
* 获取请求ID。
|
||||
*
|
||||
* @return 请求ID
|
||||
*/
|
||||
public String getRequestId() {
|
||||
return requestId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置请求ID。
|
||||
*
|
||||
* @param requestId 请求ID
|
||||
*/
|
||||
public void setRequestId(String requestId) {
|
||||
this.requestId = requestId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取链路ID。
|
||||
*
|
||||
* @return 链路ID
|
||||
*/
|
||||
public String getTraceId() {
|
||||
return traceId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置链路ID。
|
||||
*
|
||||
* @param traceId 链路ID
|
||||
*/
|
||||
public void setTraceId(String traceId) {
|
||||
this.traceId = traceId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取会话ID。
|
||||
*
|
||||
* @return 会话ID
|
||||
*/
|
||||
public String getSessionId() {
|
||||
return sessionId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置会话ID。
|
||||
*
|
||||
* @param sessionId 会话ID
|
||||
*/
|
||||
public void setSessionId(String sessionId) {
|
||||
this.sessionId = sessionId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取智能体ID。
|
||||
*
|
||||
* @return 智能体ID
|
||||
*/
|
||||
public String getAgentId() {
|
||||
return agentId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置智能体ID。
|
||||
*
|
||||
* @param agentId 智能体ID
|
||||
*/
|
||||
public void setAgentId(String agentId) {
|
||||
this.agentId = agentId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取工具调用ID。
|
||||
*
|
||||
* @return 工具调用ID
|
||||
*/
|
||||
public String getToolCallId() {
|
||||
return toolCallId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置工具调用ID。
|
||||
*
|
||||
* @param toolCallId 工具调用ID
|
||||
*/
|
||||
public void setToolCallId(String toolCallId) {
|
||||
this.toolCallId = toolCallId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取运行时上下文。
|
||||
*
|
||||
* @return 运行时上下文
|
||||
*/
|
||||
public AgentRuntimeContext getRuntimeContext() {
|
||||
return runtimeContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置运行时上下文。
|
||||
*
|
||||
* @param runtimeContext 运行时上下文
|
||||
*/
|
||||
public void setRuntimeContext(AgentRuntimeContext runtimeContext) {
|
||||
this.runtimeContext = runtimeContext == null ? new AgentRuntimeContext() : runtimeContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取元数据。
|
||||
*
|
||||
* @return 元数据
|
||||
*/
|
||||
public Map<String, Object> getMetadata() {
|
||||
return metadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置元数据。
|
||||
*
|
||||
* @param metadata 元数据
|
||||
*/
|
||||
public void setMetadata(Map<String, Object> metadata) {
|
||||
this.metadata = metadata == null ? new LinkedHashMap<>() : metadata;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.easyagents.agent.runtime.tool;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 执行动态工具。
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface AgentToolInvoker {
|
||||
|
||||
/**
|
||||
* 使用模型生成的参数调用工具。
|
||||
*
|
||||
* @param arguments 工具参数
|
||||
* @param context 调用上下文
|
||||
* @return 工具结果
|
||||
*/
|
||||
AgentToolResult invoke(Map<String, Object> arguments, AgentToolContext context);
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
package com.easyagents.agent.runtime.tool;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 动态工具返回的结果。
|
||||
*/
|
||||
public class AgentToolResult {
|
||||
|
||||
private boolean success = true;
|
||||
private String modelContent;
|
||||
private Object displayContent;
|
||||
private String errorMessage;
|
||||
private Map<String, Object> metadata = new LinkedHashMap<>();
|
||||
|
||||
/**
|
||||
* 创建成功的文本结果。
|
||||
*
|
||||
* @param content 模型可见内容
|
||||
* @return 结果
|
||||
*/
|
||||
public static AgentToolResult success(String content) {
|
||||
AgentToolResult result = new AgentToolResult();
|
||||
result.setModelContent(content);
|
||||
result.setDisplayContent(content);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建失败结果。
|
||||
*
|
||||
* @param errorMessage 错误消息
|
||||
* @return 结果
|
||||
*/
|
||||
public static AgentToolResult failure(String errorMessage) {
|
||||
AgentToolResult result = new AgentToolResult();
|
||||
result.setSuccess(false);
|
||||
result.setErrorMessage(errorMessage);
|
||||
result.setModelContent(errorMessage);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回执行是否成功。
|
||||
*
|
||||
* @return 成功时为 true
|
||||
*/
|
||||
public boolean isSuccess() {
|
||||
return success;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置成功标记。
|
||||
*
|
||||
* @param success 成功标记
|
||||
*/
|
||||
public void setSuccess(boolean success) {
|
||||
this.success = success;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取模型可见内容。
|
||||
*
|
||||
* @return 模型可见内容
|
||||
*/
|
||||
public String getModelContent() {
|
||||
return modelContent;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置模型可见内容。
|
||||
*
|
||||
* @param modelContent 模型可见内容
|
||||
*/
|
||||
public void setModelContent(String modelContent) {
|
||||
this.modelContent = modelContent;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取展示内容。
|
||||
*
|
||||
* @return 展示内容
|
||||
*/
|
||||
public Object getDisplayContent() {
|
||||
return displayContent;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置展示内容。
|
||||
*
|
||||
* @param displayContent 展示内容
|
||||
*/
|
||||
public void setDisplayContent(Object displayContent) {
|
||||
this.displayContent = displayContent;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取错误消息。
|
||||
*
|
||||
* @return 错误消息
|
||||
*/
|
||||
public String getErrorMessage() {
|
||||
return errorMessage;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置错误消息。
|
||||
*
|
||||
* @param errorMessage 错误消息
|
||||
*/
|
||||
public void setErrorMessage(String errorMessage) {
|
||||
this.errorMessage = errorMessage;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取元数据。
|
||||
*
|
||||
* @return 元数据
|
||||
*/
|
||||
public Map<String, Object> getMetadata() {
|
||||
return metadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置元数据。
|
||||
*
|
||||
* @param metadata 元数据
|
||||
*/
|
||||
public void setMetadata(Map<String, Object> metadata) {
|
||||
this.metadata = metadata == null ? new LinkedHashMap<>() : metadata;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
package com.easyagents.agent.runtime.tool;
|
||||
|
||||
import com.easyagents.agent.runtime.hitl.AgentToolApprovalRequest;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 动态工具声明。
|
||||
*/
|
||||
public class AgentToolSpec {
|
||||
|
||||
private String name;
|
||||
private String description;
|
||||
private AgentToolCategory category = AgentToolCategory.CUSTOM;
|
||||
private Map<String, Object> parametersSchema = new LinkedHashMap<>();
|
||||
private Map<String, Object> outputSchema = new LinkedHashMap<>();
|
||||
private AgentToolVisibility visibility = AgentToolVisibility.VISIBLE;
|
||||
private boolean approvalRequired;
|
||||
private AgentToolApprovalRequest approvalRequest = new AgentToolApprovalRequest();
|
||||
private Map<String, Object> metadata = new LinkedHashMap<>();
|
||||
|
||||
/**
|
||||
* 获取工具名称。
|
||||
*
|
||||
* @return 工具名称
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置工具名称。
|
||||
*
|
||||
* @param name 工具名称
|
||||
*/
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取描述。
|
||||
*
|
||||
* @return 描述
|
||||
*/
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置描述。
|
||||
*
|
||||
* @param description 描述
|
||||
*/
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取分类。
|
||||
*
|
||||
* @return 分类
|
||||
*/
|
||||
public AgentToolCategory getCategory() {
|
||||
return category;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置分类。
|
||||
*
|
||||
* @param category 分类
|
||||
*/
|
||||
public void setCategory(AgentToolCategory category) {
|
||||
this.category = category == null ? AgentToolCategory.CUSTOM : category;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取参数 JSON Schema。
|
||||
*
|
||||
* @return 参数 Schema
|
||||
*/
|
||||
public Map<String, Object> getParametersSchema() {
|
||||
return parametersSchema;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置参数 JSON Schema。
|
||||
*
|
||||
* @param parametersSchema 参数 Schema
|
||||
*/
|
||||
public void setParametersSchema(Map<String, Object> parametersSchema) {
|
||||
this.parametersSchema = parametersSchema == null ? new LinkedHashMap<>() : parametersSchema;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取输出 Schema。
|
||||
*
|
||||
* @return 输出 Schema
|
||||
*/
|
||||
public Map<String, Object> getOutputSchema() {
|
||||
return outputSchema;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置输出 Schema。
|
||||
*
|
||||
* @param outputSchema 输出 Schema
|
||||
*/
|
||||
public void setOutputSchema(Map<String, Object> outputSchema) {
|
||||
this.outputSchema = outputSchema == null ? new LinkedHashMap<>() : outputSchema;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取可见性策略。
|
||||
*
|
||||
* @return 可见性策略
|
||||
*/
|
||||
public AgentToolVisibility getVisibility() {
|
||||
return visibility;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置可见性策略。
|
||||
*
|
||||
* @param visibility 可见性策略
|
||||
*/
|
||||
public void setVisibility(AgentToolVisibility visibility) {
|
||||
this.visibility = visibility == null ? AgentToolVisibility.VISIBLE : visibility;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回工具执行前是否需要人工审批。
|
||||
*
|
||||
* @return 需要审批时为 true
|
||||
*/
|
||||
public boolean isApprovalRequired() {
|
||||
return approvalRequired;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置工具执行前是否需要人工审批。
|
||||
*
|
||||
* @param approvalRequired 审批标记
|
||||
*/
|
||||
public void setApprovalRequired(boolean approvalRequired) {
|
||||
this.approvalRequired = approvalRequired;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取工具执行审批请求。
|
||||
*
|
||||
* @return 工具执行审批请求
|
||||
*/
|
||||
public AgentToolApprovalRequest getApprovalRequest() {
|
||||
return approvalRequest;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置工具执行审批请求。
|
||||
*
|
||||
* @param approvalRequest 工具执行审批请求
|
||||
*/
|
||||
public void setApprovalRequest(AgentToolApprovalRequest approvalRequest) {
|
||||
this.approvalRequest = approvalRequest == null ? new AgentToolApprovalRequest() : approvalRequest;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取元数据。
|
||||
*
|
||||
* @return 元数据
|
||||
*/
|
||||
public Map<String, Object> getMetadata() {
|
||||
return metadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置元数据。
|
||||
*
|
||||
* @param metadata 元数据
|
||||
*/
|
||||
public void setMetadata(Map<String, Object> metadata) {
|
||||
this.metadata = metadata == null ? new LinkedHashMap<>() : metadata;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.easyagents.agent.runtime.tool;
|
||||
|
||||
/**
|
||||
* 工具执行可见性策略。
|
||||
*/
|
||||
public enum AgentToolVisibility {
|
||||
VISIBLE,
|
||||
HIDDEN,
|
||||
MODEL_ONLY
|
||||
}
|
||||
@@ -0,0 +1,951 @@
|
||||
package com.easyagents.agent.runtime.agentscope;
|
||||
|
||||
import com.easyagents.agent.runtime.AgentDefinition;
|
||||
import com.easyagents.agent.runtime.AgentRunRequest;
|
||||
import com.easyagents.agent.runtime.event.AgentRuntimeEvent;
|
||||
import com.easyagents.agent.runtime.event.AgentRuntimeEventType;
|
||||
import com.easyagents.agent.runtime.hitl.AgentResumeToken;
|
||||
import com.easyagents.agent.runtime.hitl.AgentToolApprovalCoordinator;
|
||||
import com.easyagents.agent.runtime.hitl.AgentToolApprovalRequest;
|
||||
import com.easyagents.agent.runtime.hitl.AgentToolApprovalResponse;
|
||||
import com.easyagents.agent.runtime.knowledge.AgentKnowledgeDocument;
|
||||
import com.easyagents.agent.runtime.knowledge.AgentKnowledgeRetrievalResult;
|
||||
import com.easyagents.agent.runtime.knowledge.AgentKnowledgeSpec;
|
||||
import com.easyagents.agent.runtime.memory.AgentMemoryCompressionParameter;
|
||||
import com.easyagents.agent.runtime.memory.AgentMemoryPolicy;
|
||||
import com.easyagents.agent.runtime.memory.AgentMemorySnapshot;
|
||||
import com.easyagents.agent.runtime.message.*;
|
||||
import com.easyagents.agent.runtime.model.AgentModelProviderType;
|
||||
import com.easyagents.agent.runtime.model.AgentModelSpec;
|
||||
import com.easyagents.agent.runtime.persistence.AgentPersistencePolicy;
|
||||
import com.easyagents.agent.runtime.persistence.AgentRuntimeState;
|
||||
import com.easyagents.agent.runtime.persistence.json.JsonAgentSessionStore;
|
||||
import com.easyagents.agent.runtime.persistence.memory.InMemoryAgentSessionStore;
|
||||
import com.easyagents.agent.runtime.skill.AgentSkillBoxSpec;
|
||||
import com.easyagents.agent.runtime.skill.AgentSkillSpec;
|
||||
import com.easyagents.agent.runtime.tool.AgentToolResult;
|
||||
import com.easyagents.agent.runtime.tool.AgentToolSpec;
|
||||
import io.agentscope.core.ReActAgent;
|
||||
import io.agentscope.core.agent.Event;
|
||||
import io.agentscope.core.agent.EventType;
|
||||
import io.agentscope.core.hook.ErrorEvent;
|
||||
import io.agentscope.core.hook.HookEvent;
|
||||
import io.agentscope.core.hook.ReasoningChunkEvent;
|
||||
import io.agentscope.core.memory.autocontext.AutoContextConfig;
|
||||
import io.agentscope.core.message.*;
|
||||
import io.agentscope.core.model.AnthropicChatModel;
|
||||
import io.agentscope.core.model.GeminiChatModel;
|
||||
import io.agentscope.core.model.OpenAIChatModel;
|
||||
import io.agentscope.core.rag.Knowledge;
|
||||
import io.agentscope.core.rag.model.Document;
|
||||
import io.agentscope.core.rag.model.RetrieveConfig;
|
||||
import io.agentscope.core.session.Session;
|
||||
import io.agentscope.core.skill.SkillBox;
|
||||
import io.agentscope.core.tool.AgentTool;
|
||||
import io.agentscope.core.tool.ToolCallParam;
|
||||
import io.agentscope.core.tool.Toolkit;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import reactor.core.publisher.Sinks;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 在不发起真实模型网络调用的情况下测试 AgentScope 适配行为。
|
||||
*/
|
||||
public class AgentScopeAdapterTest {
|
||||
|
||||
@Test
|
||||
public void shouldBuildReActAgentFromDefinition() {
|
||||
AgentRunRequest request = request();
|
||||
AgentScopeReActRuntime runtime = fakeRuntime();
|
||||
|
||||
ReActAgent agent = runtime.buildAgent(request, Sinks.many().unicast().onBackpressureBuffer());
|
||||
|
||||
Assert.assertEquals("agent-name", agent.getName());
|
||||
Assert.assertEquals("system", agent.getSysPrompt());
|
||||
Assert.assertEquals(3, agent.getMaxIters());
|
||||
Assert.assertNotNull(agent.getMemory());
|
||||
Assert.assertNotNull(agent.getToolkit().getTool("echo"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldRegisterAndCallDynamicTool() {
|
||||
AgentRunRequest request = request();
|
||||
AgentToolSpec toolSpec = request.getAgentDefinition().getToolSpecs().get(0);
|
||||
AgentTool tool = new AgentScopeToolAdapter().adapt(toolSpec, request.getToolInvokers().get("echo"), request);
|
||||
ToolUseBlock block = ToolUseBlock.builder()
|
||||
.id("call-1")
|
||||
.name("echo")
|
||||
.input(Map.of("text", "hello"))
|
||||
.build();
|
||||
|
||||
String result = ((TextBlock) tool.callAsync(ToolCallParam.builder()
|
||||
.toolUseBlock(block)
|
||||
.input(Map.of("text", "hello"))
|
||||
.build())
|
||||
.block()
|
||||
.getOutput()
|
||||
.get(0)).getText();
|
||||
|
||||
Assert.assertTrue(result.contains("hello"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldEmitToolCallAndResultEventsFromToolkitCallback() throws Exception {
|
||||
AgentRunRequest request = request();
|
||||
Sinks.Many<AgentRuntimeEvent> sink = Sinks.many().unicast().onBackpressureBuffer();
|
||||
ReActAgent agent = fakeRuntime().buildAgent(request, sink);
|
||||
CompletableFuture<List<AgentRuntimeEvent>> eventsFuture = sink.asFlux().take(2).collectList().toFuture();
|
||||
ToolUseBlock block = ToolUseBlock.builder()
|
||||
.id("call-2")
|
||||
.name("echo")
|
||||
.input(Map.of("text", "hello"))
|
||||
.build();
|
||||
|
||||
agent.getToolkit().getTool("echo").callAsync(ToolCallParam.builder()
|
||||
.toolUseBlock(block)
|
||||
.input(Map.of("text", "hello"))
|
||||
.build())
|
||||
.block();
|
||||
List<AgentRuntimeEvent> events = eventsFuture.get(3, TimeUnit.SECONDS);
|
||||
|
||||
Assert.assertTrue(events.stream().anyMatch(event -> event.getEventType() == AgentRuntimeEventType.TOOL_CALL));
|
||||
Assert.assertTrue(events.stream().anyMatch(event -> event.getEventType() == AgentRuntimeEventType.TOOL_RESULT));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldEmitNormalToolEventBeforeSkillActivated() throws Exception {
|
||||
AgentRunRequest request = requestWithSkillBoundTool();
|
||||
Sinks.Many<AgentRuntimeEvent> sink = Sinks.many().unicast().onBackpressureBuffer();
|
||||
ReActAgent agent = fakeRuntime().buildAgent(request, sink);
|
||||
CompletableFuture<List<AgentRuntimeEvent>> eventsFuture = sink.asFlux().take(2).collectList().toFuture();
|
||||
ToolUseBlock block = ToolUseBlock.builder()
|
||||
.id("call-skill-tool")
|
||||
.name("echo")
|
||||
.input(Map.of("text", "hello"))
|
||||
.build();
|
||||
|
||||
agent.getToolkit().getTool("echo").callAsync(ToolCallParam.builder()
|
||||
.toolUseBlock(block)
|
||||
.input(Map.of("text", "hello"))
|
||||
.build())
|
||||
.block();
|
||||
List<AgentRuntimeEvent> events = eventsFuture.get(3, TimeUnit.SECONDS);
|
||||
|
||||
Assert.assertEquals(AgentRuntimeEventType.TOOL_CALL, events.get(0).getEventType());
|
||||
Assert.assertEquals(AgentRuntimeEventType.TOOL_RESULT, events.get(1).getEventType());
|
||||
Assert.assertFalse(events.get(0).getPayload().containsKey("skillId"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldEmitSkillStepAfterSkillActivated() throws Exception {
|
||||
AgentRunRequest request = requestWithSkillBoundTool();
|
||||
com.easyagents.agent.runtime.skill.AgentSkillRuntimeContext skillContext =
|
||||
com.easyagents.agent.runtime.skill.AgentSkillRuntimeContext.from(request.getAgentDefinition().getSkillBoxSpec());
|
||||
skillContext.activateSkill("skill-1");
|
||||
Sinks.Many<AgentRuntimeEvent> sink = Sinks.many().unicast().onBackpressureBuffer();
|
||||
AgentTool tool = new AgentScopeToolAdapter().adapt(request.getAgentDefinition().getToolSpecs().get(0),
|
||||
request.getToolInvokers().get("echo"), request, AgentToolApprovalCoordinator.disabled(), sink,
|
||||
skillContext, skillContext.getToolBinding("echo"));
|
||||
CompletableFuture<List<AgentRuntimeEvent>> eventsFuture = sink.asFlux().take(2).collectList().toFuture();
|
||||
|
||||
tool.callAsync(ToolCallParam.builder()
|
||||
.toolUseBlock(ToolUseBlock.builder()
|
||||
.id("call-skill-tool")
|
||||
.name("echo")
|
||||
.input(Map.of("text", "hello"))
|
||||
.build())
|
||||
.input(Map.of("text", "hello"))
|
||||
.build())
|
||||
.block();
|
||||
List<AgentRuntimeEvent> events = eventsFuture.get(3, TimeUnit.SECONDS);
|
||||
|
||||
Assert.assertTrue(events.stream().allMatch(event -> event.getEventType() == AgentRuntimeEventType.SKILL_STEP));
|
||||
Assert.assertEquals("skill-1", events.get(0).getPayload().get("skillId"));
|
||||
Assert.assertEquals("TOOL_CALL", events.get(0).getPayload().get("stepType"));
|
||||
Assert.assertEquals("TOOL_RESULT", events.get(1).getPayload().get("stepType"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldEmitToolApprovalWhenToolRequiresApproval() throws Exception {
|
||||
AgentRunRequest enabled = request();
|
||||
AgentToolApprovalRequest approvalRequest = new AgentToolApprovalRequest();
|
||||
approvalRequest.setApprovalPrompt("确认执行?");
|
||||
enabled.getAgentDefinition().getToolSpecs().get(0).setApprovalRequired(true);
|
||||
enabled.getAgentDefinition().getToolSpecs().get(0).setApprovalRequest(approvalRequest);
|
||||
AgentToolApprovalCoordinator coordinator = AgentToolApprovalCoordinator.enabled();
|
||||
Sinks.Many<AgentRuntimeEvent> sink = Sinks.many().unicast().onBackpressureBuffer();
|
||||
AgentTool tool = new AgentScopeToolAdapter().adapt(enabled.getAgentDefinition().getToolSpecs().get(0),
|
||||
enabled.getToolInvokers().get("echo"), enabled, coordinator, sink);
|
||||
java.util.concurrent.CountDownLatch approvalLatch = new java.util.concurrent.CountDownLatch(1);
|
||||
java.util.concurrent.CopyOnWriteArrayList<AgentRuntimeEvent> events = new java.util.concurrent.CopyOnWriteArrayList<>();
|
||||
sink.asFlux().subscribe(event -> {
|
||||
events.add(event);
|
||||
if (event.getEventType() == AgentRuntimeEventType.TOOL_APPROVAL_REQUIRED) {
|
||||
approvalLatch.countDown();
|
||||
}
|
||||
});
|
||||
tool.callAsync(ToolCallParam.builder()
|
||||
.toolUseBlock(ToolUseBlock.builder()
|
||||
.id("call-hitl")
|
||||
.name("echo")
|
||||
.input(Map.of("text", "hello"))
|
||||
.build())
|
||||
.input(Map.of("text", "hello"))
|
||||
.build())
|
||||
.subscribe(result -> {
|
||||
}, error -> {
|
||||
});
|
||||
Assert.assertTrue(approvalLatch.await(5, TimeUnit.SECONDS));
|
||||
AgentRuntimeEvent approval = events.stream()
|
||||
.filter(event -> event.getEventType() == AgentRuntimeEventType.TOOL_APPROVAL_REQUIRED)
|
||||
.findFirst()
|
||||
.orElseThrow(AssertionError::new);
|
||||
Assert.assertEquals("确认执行?", approval.getPayload().get("approvalPrompt"));
|
||||
Assert.assertNotNull(approval.getPayload().get("resumeToken"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldContinueToolExecutionAfterApproval() throws Exception {
|
||||
AgentRunRequest request = request();
|
||||
AgentToolApprovalRequest approvalRequest = new AgentToolApprovalRequest();
|
||||
approvalRequest.setApprovalPrompt("确认执行?");
|
||||
request.getAgentDefinition().getToolSpecs().get(0).setApprovalRequired(true);
|
||||
request.getAgentDefinition().getToolSpecs().get(0).setApprovalRequest(approvalRequest);
|
||||
|
||||
AgentToolApprovalCoordinator coordinator = AgentToolApprovalCoordinator.enabled();
|
||||
Sinks.Many<AgentRuntimeEvent> sink = Sinks.many().unicast().onBackpressureBuffer();
|
||||
AgentTool tool = new AgentScopeToolAdapter().adapt(request.getAgentDefinition().getToolSpecs().get(0),
|
||||
request.getToolInvokers().get("echo"), request, coordinator, sink);
|
||||
java.util.List<AgentRuntimeEvent> events = new java.util.concurrent.CopyOnWriteArrayList<>();
|
||||
java.util.concurrent.CountDownLatch approvalLatch = new java.util.concurrent.CountDownLatch(1);
|
||||
java.util.concurrent.atomic.AtomicReference<Throwable> errorRef = new java.util.concurrent.atomic.AtomicReference<>();
|
||||
sink.asFlux().subscribe(event -> {
|
||||
events.add(event);
|
||||
if (event.getEventType() == AgentRuntimeEventType.TOOL_APPROVAL_REQUIRED) {
|
||||
approvalLatch.countDown();
|
||||
}
|
||||
});
|
||||
java.util.concurrent.CompletableFuture<io.agentscope.core.message.ToolResultBlock> future = tool.callAsync(
|
||||
ToolCallParam.builder()
|
||||
.toolUseBlock(ToolUseBlock.builder()
|
||||
.id("call-hitl")
|
||||
.name("echo")
|
||||
.input(Map.of("text", "hello"))
|
||||
.build())
|
||||
.input(Map.of("text", "hello"))
|
||||
.build()).toFuture();
|
||||
Assert.assertTrue(approvalLatch.await(5, TimeUnit.SECONDS));
|
||||
AgentRuntimeEvent approval = events.stream()
|
||||
.filter(event -> event.getEventType() == AgentRuntimeEventType.TOOL_APPROVAL_REQUIRED)
|
||||
.findFirst()
|
||||
.orElseThrow(AssertionError::new);
|
||||
|
||||
AgentToolApprovalResponse response = new AgentToolApprovalResponse();
|
||||
AgentResumeToken token = AgentResumeToken.create();
|
||||
token.setValue(String.valueOf(approval.getPayload().get("resumeToken")));
|
||||
response.setResumeToken(token);
|
||||
response.setApproved(true);
|
||||
|
||||
coordinator.submit(response);
|
||||
io.agentscope.core.message.ToolResultBlock resultBlock = future.get(5, TimeUnit.SECONDS);
|
||||
Assert.assertTrue(events.stream().anyMatch(event -> event.getEventType() == AgentRuntimeEventType.TOOL_RESULT));
|
||||
Assert.assertNotNull(resultBlock);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldCancelToolExecutionAfterRejection() throws Exception {
|
||||
AgentRunRequest request = request();
|
||||
AgentToolApprovalRequest approvalRequest = new AgentToolApprovalRequest();
|
||||
approvalRequest.setApprovalPrompt("确认执行?");
|
||||
request.getAgentDefinition().getToolSpecs().get(0).setApprovalRequired(true);
|
||||
request.getAgentDefinition().getToolSpecs().get(0).setApprovalRequest(approvalRequest);
|
||||
|
||||
AgentToolApprovalCoordinator coordinator = AgentToolApprovalCoordinator.enabled();
|
||||
Sinks.Many<AgentRuntimeEvent> sink = Sinks.many().unicast().onBackpressureBuffer();
|
||||
AgentTool tool = new AgentScopeToolAdapter().adapt(request.getAgentDefinition().getToolSpecs().get(0),
|
||||
request.getToolInvokers().get("echo"), request, coordinator, sink);
|
||||
java.util.List<AgentRuntimeEvent> events = new java.util.concurrent.CopyOnWriteArrayList<>();
|
||||
sink.asFlux().subscribe(events::add);
|
||||
java.util.concurrent.CompletableFuture<io.agentscope.core.message.ToolResultBlock> future = tool.callAsync(
|
||||
ToolCallParam.builder()
|
||||
.toolUseBlock(ToolUseBlock.builder()
|
||||
.id("call-hitl")
|
||||
.name("echo")
|
||||
.input(Map.of("text", "hello"))
|
||||
.build())
|
||||
.input(Map.of("text", "hello"))
|
||||
.build()).toFuture();
|
||||
AgentRuntimeEvent approval = null;
|
||||
long deadline = System.currentTimeMillis() + 5000L;
|
||||
while (System.currentTimeMillis() < deadline) {
|
||||
approval = events.stream()
|
||||
.filter(event -> event.getEventType() == AgentRuntimeEventType.TOOL_APPROVAL_REQUIRED)
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
if (approval != null) {
|
||||
break;
|
||||
}
|
||||
Thread.sleep(50L);
|
||||
}
|
||||
if (approval == null) {
|
||||
throw new AssertionError("未收到审批事件");
|
||||
}
|
||||
|
||||
AgentToolApprovalResponse response = new AgentToolApprovalResponse();
|
||||
AgentResumeToken token = AgentResumeToken.create();
|
||||
token.setValue(String.valueOf(approval.getPayload().get("resumeToken")));
|
||||
response.setResumeToken(token);
|
||||
response.setApproved(false);
|
||||
response.setRejectReason("拒绝执行");
|
||||
|
||||
coordinator.submit(response);
|
||||
try {
|
||||
future.get(5, TimeUnit.SECONDS);
|
||||
Assert.fail("拒绝后不应返回工具结果。");
|
||||
} catch (java.util.concurrent.ExecutionException exception) {
|
||||
Assert.assertTrue(exception.getCause() instanceof com.easyagents.agent.runtime.hitl.AgentToolApprovalRejectedException);
|
||||
}
|
||||
Assert.assertFalse(events.stream().anyMatch(event -> event.getEventType() == AgentRuntimeEventType.TOOL_RESULT));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldNotDuplicateToolApprovalFromRuntimeEventMapper() throws Exception {
|
||||
AgentRunRequest request = request();
|
||||
AgentScopeReActRuntime runtime = fakeRuntime();
|
||||
java.lang.reflect.Method method = AgentScopeReActRuntime.class.getDeclaredMethod(
|
||||
"mapEvent", AgentRunRequest.class, Event.class);
|
||||
method.setAccessible(true);
|
||||
ToolResultBlock resultBlock = ToolResultBlock.builder()
|
||||
.id("call-hitl")
|
||||
.name("echo")
|
||||
.output(TextBlock.builder().text("need confirm").build())
|
||||
.metadata(Map.of(ToolResultBlock.METADATA_SUSPENDED, true))
|
||||
.build();
|
||||
Msg message = Msg.builder()
|
||||
.role(MsgRole.TOOL)
|
||||
.content(resultBlock)
|
||||
.build();
|
||||
Event event = new Event(EventType.TOOL_RESULT, message, true);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
List<AgentRuntimeEvent> events = (List<AgentRuntimeEvent>) method.invoke(runtime, request, event);
|
||||
|
||||
Assert.assertTrue(events.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldMapSkillLoadEventsFromAgentScopeToolEvents() throws Exception {
|
||||
AgentRunRequest request = requestWithSkillBoundTool();
|
||||
AgentScopeReActRuntime runtime = fakeRuntime();
|
||||
java.lang.reflect.Method method = AgentScopeReActRuntime.class.getDeclaredMethod(
|
||||
"mapEvent", AgentRunRequest.class, Event.class,
|
||||
com.easyagents.agent.runtime.skill.AgentSkillRuntimeContext.class);
|
||||
method.setAccessible(true);
|
||||
com.easyagents.agent.runtime.skill.AgentSkillRuntimeContext skillContext =
|
||||
com.easyagents.agent.runtime.skill.AgentSkillRuntimeContext.from(request.getAgentDefinition().getSkillBoxSpec());
|
||||
ToolUseBlock toolUseBlock = ToolUseBlock.builder()
|
||||
.id("skill-call-1")
|
||||
.name(com.easyagents.agent.runtime.skill.AgentSkillRuntimeContext.LOAD_SKILL_TOOL_NAME)
|
||||
.input(Map.of("skillId", "skill-1", "path", "SKILL.md"))
|
||||
.build();
|
||||
Msg reasoning = Msg.builder()
|
||||
.id("reasoning-msg")
|
||||
.role(MsgRole.ASSISTANT)
|
||||
.content(toolUseBlock)
|
||||
.build();
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
List<AgentRuntimeEvent> callEvents = (List<AgentRuntimeEvent>) method.invoke(
|
||||
runtime, request, new Event(EventType.REASONING, reasoning, false), skillContext);
|
||||
|
||||
ToolResultBlock resultBlock = ToolResultBlock.of(
|
||||
"skill-call-1",
|
||||
com.easyagents.agent.runtime.skill.AgentSkillRuntimeContext.LOAD_SKILL_TOOL_NAME,
|
||||
TextBlock.builder().text("Successfully loaded skill: skill-1").build());
|
||||
Msg toolResult = Msg.builder()
|
||||
.id("tool-msg")
|
||||
.role(MsgRole.TOOL)
|
||||
.content(resultBlock)
|
||||
.build();
|
||||
@SuppressWarnings("unchecked")
|
||||
List<AgentRuntimeEvent> resultEvents = (List<AgentRuntimeEvent>) method.invoke(
|
||||
runtime, request, new Event(EventType.TOOL_RESULT, toolResult, true), skillContext);
|
||||
|
||||
Assert.assertTrue(callEvents.stream().anyMatch(event -> event.getEventType() == AgentRuntimeEventType.SKILL_CALL));
|
||||
AgentRuntimeEvent resultEvent = resultEvents.get(0);
|
||||
Assert.assertEquals(AgentRuntimeEventType.SKILL_RESULT, resultEvent.getEventType());
|
||||
Assert.assertEquals("skill-1", resultEvent.getPayload().get("skillId"));
|
||||
Assert.assertEquals("SKILL.md", resultEvent.getPayload().get("path"));
|
||||
Assert.assertTrue(skillContext.isSkillActive("skill-1"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldAttachStructuredMessageOnLastAgentResult() throws Exception {
|
||||
AgentRunRequest request = request();
|
||||
AgentScopeReActRuntime runtime = fakeRuntime();
|
||||
java.lang.reflect.Method method = AgentScopeReActRuntime.class.getDeclaredMethod(
|
||||
"mapEvent", AgentRunRequest.class, Event.class);
|
||||
method.setAccessible(true);
|
||||
Msg message = Msg.builder()
|
||||
.id("assistant-msg")
|
||||
.role(MsgRole.ASSISTANT)
|
||||
.content(TextBlock.builder().text("final answer").build())
|
||||
.build();
|
||||
Event event = new Event(EventType.AGENT_RESULT, message, true);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
List<AgentRuntimeEvent> events = (List<AgentRuntimeEvent>) method.invoke(runtime, request, event);
|
||||
|
||||
AgentRuntimeEvent delta = events.get(0);
|
||||
Assert.assertEquals(AgentRuntimeEventType.MESSAGE_DELTA, delta.getEventType());
|
||||
Assert.assertNotNull(delta.getMessage());
|
||||
Assert.assertEquals("assistant-msg", delta.getMessage().getMessageId());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldNotEmitReasoningFromHook() {
|
||||
AgentRunRequest request = request();
|
||||
AgentScopeEventHook hook = new AgentScopeEventHook(request);
|
||||
ReActAgent agent = fakeRuntime().buildAgent(request, Sinks.many().unicast().onBackpressureBuffer());
|
||||
Msg chunk = Msg.builder()
|
||||
.role(MsgRole.ASSISTANT)
|
||||
.textContent("thinking")
|
||||
.build();
|
||||
|
||||
hook.onEvent(new ReasoningChunkEvent(agent, "run-1", null, chunk, chunk)).block();
|
||||
Assert.assertNotNull(chunk);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldAggregateKnowledgeAndPreserveMetadata() {
|
||||
AgentRunRequest request = request();
|
||||
AgentKnowledgeSpec second = knowledge("kb-2", "KB2");
|
||||
second.getMetadata().put("permissionScope", "team");
|
||||
request.getAgentDefinition().getKnowledgeSpecs().add(second);
|
||||
request.getKnowledgeRetrievers().put("kb-2", retrievalRequest -> {
|
||||
AgentKnowledgeDocument document = doc("doc-2", "chunk-2", "content-2", 0.95D);
|
||||
document.getMetadata().put("pageNo", 2);
|
||||
return AgentKnowledgeRetrievalResult.of(List.of(document));
|
||||
});
|
||||
Knowledge knowledge = new AgentScopeKnowledgeAdapter().createAggregateKnowledge(request);
|
||||
|
||||
List<Document> documents = knowledge.retrieve("query", RetrieveConfig.builder().limit(2).scoreThreshold(0D).build()).block();
|
||||
|
||||
Assert.assertEquals(2, documents.size());
|
||||
Map<String, Object> payload = documents.get(0).getPayload();
|
||||
Assert.assertTrue(payload.containsKey("knowledgeMetadata"));
|
||||
Assert.assertTrue(payload.containsKey("documentMetadata"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldEmitKnowledgeRetrievalEvent() throws Exception {
|
||||
AgentRunRequest request = request();
|
||||
Sinks.Many<AgentRuntimeEvent> sink = Sinks.many().unicast().onBackpressureBuffer();
|
||||
Knowledge knowledge = new AgentScopeKnowledgeAdapter().createAggregateKnowledge(request, sink);
|
||||
CompletableFuture<List<AgentRuntimeEvent>> eventsFuture = sink.asFlux().take(1).collectList().toFuture();
|
||||
|
||||
knowledge.retrieve("query", RetrieveConfig.builder().limit(1).scoreThreshold(0.2D).build()).block();
|
||||
AgentRuntimeEvent event = eventsFuture.get(3, TimeUnit.SECONDS).get(0);
|
||||
|
||||
Assert.assertEquals(AgentRuntimeEventType.KNOWLEDGE_RETRIEVAL, event.getEventType());
|
||||
Assert.assertEquals("kb-1", event.getPayload().get("knowledgeId"));
|
||||
Assert.assertEquals(1, event.getPayload().get("documentCount"));
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Map<String, Object>> documents = (List<Map<String, Object>>) event.getPayload().get("documents");
|
||||
Assert.assertFalse(documents.get(0).containsKey("content"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldFailWhenKnowledgeRetrieverMissing() {
|
||||
AgentRunRequest request = request();
|
||||
request.getKnowledgeRetrievers().clear();
|
||||
Knowledge knowledge = new AgentScopeKnowledgeAdapter().createAggregateKnowledge(request);
|
||||
|
||||
try {
|
||||
knowledge.retrieve("query", RetrieveConfig.builder().limit(2).scoreThreshold(0D).build()).block();
|
||||
Assert.fail("Expected missing knowledge retriever to fail.");
|
||||
} catch (Exception exception) {
|
||||
Assert.assertTrue(exception.getMessage().contains("Knowledge retriever is required"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldUseKnowledgeSpecLimitAsAggregateDefault() {
|
||||
AgentRunRequest request = request();
|
||||
AgentKnowledgeSpec second = knowledge("kb-2", "KB2");
|
||||
second.setLimit(3);
|
||||
request.getAgentDefinition().getKnowledgeSpecs().get(0).setLimit(2);
|
||||
request.getAgentDefinition().getKnowledgeSpecs().add(second);
|
||||
ReActAgent agent = fakeRuntime().buildAgent(request, Sinks.many().unicast().onBackpressureBuffer());
|
||||
|
||||
Assert.assertNotNull(agent);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldUseMinimumKnowledgeScoreThresholdAsAggregateDefault() throws Exception {
|
||||
AgentRunRequest request = request();
|
||||
request.getAgentDefinition().getKnowledgeSpecs().get(0).setScoreThreshold(0.4D);
|
||||
AgentKnowledgeSpec second = knowledge("kb-2", "KB2");
|
||||
second.setScoreThreshold(0.2D);
|
||||
request.getAgentDefinition().getKnowledgeSpecs().add(second);
|
||||
AgentScopeReActRuntime runtime = fakeRuntime();
|
||||
java.lang.reflect.Method method = AgentScopeReActRuntime.class.getDeclaredMethod(
|
||||
"defaultRetrieveConfig", AgentDefinition.class);
|
||||
method.setAccessible(true);
|
||||
|
||||
RetrieveConfig config = (RetrieveConfig) method.invoke(runtime, request.getAgentDefinition());
|
||||
|
||||
Assert.assertEquals(0.2D, config.getScoreThreshold(), 0.0001D);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldRoundTripStructuredMessageBlocks() {
|
||||
AgentMessage message = new AgentMessage();
|
||||
message.setMessageId("msg-1");
|
||||
message.setRole(AgentMessageRole.ASSISTANT);
|
||||
message.getMetadata().put("scene", "chat");
|
||||
message.getContentBlocks().add(new AgentTextBlock("hello"));
|
||||
message.getContentBlocks().add(new AgentThinkingBlock("thinking"));
|
||||
AgentToolUseBlock toolUse = new AgentToolUseBlock("call-1", "echo", Map.of("text", "hi"));
|
||||
toolUse.setContent("raw-call");
|
||||
message.getContentBlocks().add(toolUse);
|
||||
AgentToolResultBlock toolResult = new AgentToolResultBlock("call-1", "echo");
|
||||
toolResult.setSuspended(true);
|
||||
toolResult.getMetadata().put("status", "ok");
|
||||
toolResult.getOutput().add(new AgentTextBlock("result"));
|
||||
message.getContentBlocks().add(toolResult);
|
||||
AgentMediaBlock media = new AgentMediaBlock("image");
|
||||
media.setUrl("https://example.com/a.png");
|
||||
media.setMimeType("image/png");
|
||||
message.getContentBlocks().add(media);
|
||||
|
||||
AgentScopeMessageAdapter adapter = new AgentScopeMessageAdapter();
|
||||
Msg msg = adapter.toMsg(message);
|
||||
AgentMessage converted = adapter.toAgentMessage(msg);
|
||||
|
||||
Assert.assertEquals("msg-1", converted.getMessageId());
|
||||
Assert.assertEquals(5, converted.getContentBlocks().size());
|
||||
Assert.assertEquals("hello", ((AgentTextBlock) converted.getContentBlocks().get(0)).getText());
|
||||
Assert.assertEquals("thinking", ((AgentThinkingBlock) converted.getContentBlocks().get(1)).getThinking());
|
||||
Assert.assertEquals("call-1", ((AgentToolUseBlock) converted.getContentBlocks().get(2)).getId());
|
||||
Assert.assertEquals("echo", ((AgentToolResultBlock) converted.getContentBlocks().get(3)).getName());
|
||||
Assert.assertEquals("image", ((AgentMediaBlock) converted.getContentBlocks().get(4)).getMediaKind());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldPreserveInvalidTimestampAsRawMetadata() {
|
||||
Msg msg = Msg.builder()
|
||||
.id("msg-invalid-time")
|
||||
.role(MsgRole.USER)
|
||||
.textContent("hello")
|
||||
.timestamp("invalid-time")
|
||||
.build();
|
||||
|
||||
AgentMessage converted = new AgentScopeMessageAdapter().toAgentMessage(msg);
|
||||
|
||||
Assert.assertEquals("invalid-time", converted.getMetadata().get("rawTimestamp"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldConvertUnknownBlockToExplicitFallbackText() {
|
||||
AgentUnknownBlock unknownBlock = new AgentUnknownBlock();
|
||||
unknownBlock.setSourceClassName("com.example.CustomBlock");
|
||||
unknownBlock.setSourceTypeName("custom");
|
||||
|
||||
io.agentscope.core.message.ContentBlock converted = new AgentScopeMessageAdapter().toContentBlock(unknownBlock);
|
||||
|
||||
Assert.assertTrue(converted instanceof TextBlock);
|
||||
Assert.assertTrue(((TextBlock) converted).getText().contains("unsupported content block"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldCreateAutoContextConfigFromPolicy() {
|
||||
AgentMemoryCompressionParameter parameter = new AgentMemoryCompressionParameter();
|
||||
parameter.setMsgThreshold(9);
|
||||
parameter.setLastKeep(4);
|
||||
parameter.setMaxToken(1234L);
|
||||
|
||||
AutoContextConfig config = new AgentScopeMemoryAdapter().toAutoContextConfig(parameter);
|
||||
|
||||
Assert.assertEquals(9, config.getMsgThreshold());
|
||||
Assert.assertEquals(4, config.getLastKeep());
|
||||
Assert.assertEquals(1234L, config.getMaxToken());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldMapOllamaGenerationOptions() throws Exception {
|
||||
AgentModelSpec modelSpec = new AgentModelSpec();
|
||||
modelSpec.setProviderType(AgentModelProviderType.OLLAMA);
|
||||
modelSpec.setModelName("llama");
|
||||
com.easyagents.agent.runtime.model.AgentGenerationOptions options = new com.easyagents.agent.runtime.model.AgentGenerationOptions();
|
||||
options.setTemperature(0.2D);
|
||||
options.setTopP(0.8D);
|
||||
options.setTopK(20);
|
||||
options.setMaxTokens(128);
|
||||
|
||||
Object model = new AgentScopeModelFactory().create(modelSpec, options);
|
||||
Field field = model.getClass().getDeclaredField("defaultOptions");
|
||||
field.setAccessible(true);
|
||||
io.agentscope.core.model.ollama.OllamaOptions ollamaOptions = (io.agentscope.core.model.ollama.OllamaOptions) field.get(model);
|
||||
|
||||
Assert.assertEquals(0.2D, ollamaOptions.getTemperature(), 0.0001D);
|
||||
Assert.assertEquals(0.8D, ollamaOptions.getTopP(), 0.0001D);
|
||||
Assert.assertEquals(Integer.valueOf(20), ollamaOptions.getTopK());
|
||||
Assert.assertEquals(Integer.valueOf(128), ollamaOptions.getMaxTokens());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldCreateNativeProviderModels() {
|
||||
Assert.assertTrue(createModel(AgentModelProviderType.OPENAI) instanceof OpenAIChatModel);
|
||||
Assert.assertTrue(createModel(AgentModelProviderType.ANTHROPIC) instanceof AnthropicChatModel);
|
||||
Assert.assertTrue(createModel(AgentModelProviderType.GEMINI) instanceof GeminiChatModel);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldCreateDomesticProvidersAsOpenAiCompatibleModels() {
|
||||
Assert.assertTrue(createModel(AgentModelProviderType.GLM) instanceof OpenAIChatModel);
|
||||
Assert.assertTrue(createModel(AgentModelProviderType.MINIMAX) instanceof OpenAIChatModel);
|
||||
Assert.assertTrue(createModel(AgentModelProviderType.MOONSHOT) instanceof OpenAIChatModel);
|
||||
Assert.assertTrue(createModel(AgentModelProviderType.ARK) instanceof OpenAIChatModel);
|
||||
Assert.assertTrue(createModel(AgentModelProviderType.SILICONFLOW) instanceof OpenAIChatModel);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldStoreSessionThroughAdapter() {
|
||||
InMemoryAgentSessionStore store = new InMemoryAgentSessionStore();
|
||||
Session session = new AgentScopeSessionAdapter(store);
|
||||
|
||||
session.save(AgentScopeSessionAdapter.sessionKey("s1"), "state", new TestState("v1"));
|
||||
|
||||
Assert.assertTrue(store.exists("s1"));
|
||||
Assert.assertEquals("v1", session.get(AgentScopeSessionAdapter.sessionKey("s1"), "state", TestState.class).get().value);
|
||||
Assert.assertTrue(AgentScopeSessionAdapter.toStatePersistence(AgentPersistencePolicy.memoryOnly()).memoryManaged());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldPersistSessionStateAsJsonFiles() throws Exception {
|
||||
Path directory = Files.createTempDirectory("easy-agents-session-");
|
||||
JsonAgentSessionStore store = new JsonAgentSessionStore(directory);
|
||||
Session session = new AgentScopeSessionAdapter(store);
|
||||
Msg first = Msg.builder()
|
||||
.role(MsgRole.USER)
|
||||
.content(TextBlock.builder().text("hello").build())
|
||||
.build();
|
||||
Msg second = Msg.builder()
|
||||
.role(MsgRole.ASSISTANT)
|
||||
.content(TextBlock.builder().text("world").build())
|
||||
.build();
|
||||
|
||||
session.save(AgentScopeSessionAdapter.sessionKey("s1"), "message", first);
|
||||
session.save(AgentScopeSessionAdapter.sessionKey("s1"), "messages", List.of(first, second));
|
||||
|
||||
JsonAgentSessionStore reloadedStore = new JsonAgentSessionStore(directory);
|
||||
Session reloadedSession = new AgentScopeSessionAdapter(reloadedStore);
|
||||
Msg reloaded = reloadedSession.get(AgentScopeSessionAdapter.sessionKey("s1"), "message", Msg.class).orElseThrow();
|
||||
List<Msg> messages = reloadedSession.getList(AgentScopeSessionAdapter.sessionKey("s1"), "messages", Msg.class);
|
||||
|
||||
Assert.assertTrue(reloadedStore.exists("s1"));
|
||||
Assert.assertEquals("hello", reloaded.getTextContent());
|
||||
Assert.assertEquals(2, messages.size());
|
||||
Assert.assertEquals("world", messages.get(1).getTextContent());
|
||||
}
|
||||
|
||||
@Test(expected = RuntimeException.class)
|
||||
public void shouldRejectUnsafeSessionKeysForFileStore() throws Exception {
|
||||
JsonAgentSessionStore store = new JsonAgentSessionStore(Files.createTempDirectory("easy-agents-session-"));
|
||||
|
||||
store.save("../unsafe", "state", AgentRuntimeState.of("state", new TestState("v1")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldCreateSkillBox() {
|
||||
AgentSkillSpec skill = new AgentSkillSpec();
|
||||
skill.setSkillId("skill-1");
|
||||
skill.setName("skill");
|
||||
skill.setDescription("desc");
|
||||
skill.setSkillContent("content");
|
||||
AgentSkillBoxSpec spec = new AgentSkillBoxSpec();
|
||||
spec.setSkillBoxId("box");
|
||||
spec.setSkills(List.of(skill));
|
||||
|
||||
SkillBox skillBox = new AgentScopeSkillAdapter().createSkillBox(spec, new Toolkit());
|
||||
|
||||
Assert.assertNotNull(skillBox);
|
||||
Assert.assertFalse(skillBox.getAllSkillIds().isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldExposeSkillBoundToolThroughAgentToolkit() {
|
||||
AgentRunRequest request = requestWithSkillBoundTool();
|
||||
ReActAgent agent = fakeRuntime().buildAgent(request, Sinks.many().unicast().onBackpressureBuffer());
|
||||
|
||||
Assert.assertNotNull(agent.getToolkit().getTool("echo"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldAttachSkillMetadataToApprovalEvent() throws Exception {
|
||||
AgentRunRequest request = requestWithSkillBoundTool();
|
||||
request.getAgentDefinition().getToolSpecs().get(0).setApprovalRequired(true);
|
||||
AgentToolApprovalCoordinator coordinator = AgentToolApprovalCoordinator.enabled();
|
||||
Sinks.Many<AgentRuntimeEvent> sink = Sinks.many().unicast().onBackpressureBuffer();
|
||||
com.easyagents.agent.runtime.skill.AgentSkillRuntimeContext skillContext = activatedSkillContext(request);
|
||||
AgentTool tool = new AgentScopeToolAdapter().adapt(request.getAgentDefinition().getToolSpecs().get(0),
|
||||
request.getToolInvokers().get("echo"), request, coordinator, sink,
|
||||
skillContext, skillContext.getToolBinding("echo"));
|
||||
java.util.List<AgentRuntimeEvent> events = new java.util.concurrent.CopyOnWriteArrayList<>();
|
||||
java.util.concurrent.CountDownLatch approvalLatch = new java.util.concurrent.CountDownLatch(1);
|
||||
sink.asFlux().subscribe(event -> {
|
||||
events.add(event);
|
||||
if (event.getEventType() == AgentRuntimeEventType.TOOL_APPROVAL_REQUIRED) {
|
||||
approvalLatch.countDown();
|
||||
}
|
||||
});
|
||||
|
||||
tool.callAsync(ToolCallParam.builder()
|
||||
.toolUseBlock(ToolUseBlock.builder()
|
||||
.id("call-skill-approval")
|
||||
.name("echo")
|
||||
.input(Map.of("text", "hello"))
|
||||
.build())
|
||||
.input(Map.of("text", "hello"))
|
||||
.build())
|
||||
.subscribe(result -> {
|
||||
}, error -> {
|
||||
});
|
||||
Assert.assertTrue(approvalLatch.await(5, TimeUnit.SECONDS));
|
||||
|
||||
AgentRuntimeEvent approval = events.stream()
|
||||
.filter(event -> event.getEventType() == AgentRuntimeEventType.TOOL_APPROVAL_REQUIRED)
|
||||
.findFirst()
|
||||
.orElseThrow(AssertionError::new);
|
||||
Assert.assertEquals("skill-1", approval.getPayload().get("skillId"));
|
||||
Assert.assertEquals("skill", approval.getPayload().get("skillName"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldEmitCompletedAndFailedEvents() {
|
||||
AgentRunRequest request = request();
|
||||
|
||||
Assert.assertTrue(fakeRuntime().stream(request)
|
||||
.collectList()
|
||||
.block()
|
||||
.stream()
|
||||
.anyMatch(event -> event.getEventType() == AgentRuntimeEventType.COMPLETED));
|
||||
|
||||
AgentRunRequest failed = request();
|
||||
failed.getAgentDefinition().setModelSpec(null);
|
||||
Assert.assertTrue(fakeRuntime().stream(failed)
|
||||
.collectList()
|
||||
.block()
|
||||
.stream()
|
||||
.anyMatch(event -> event.getEventType() == AgentRuntimeEventType.FAILED));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldEmitCancelledEventFromRunHandle() throws Exception {
|
||||
AgentRunRequest request = request();
|
||||
request.setCancelReason("user stopped");
|
||||
AgentScopeReActRuntime runtime = fakeRuntime();
|
||||
java.lang.reflect.Method method = AgentScopeReActRuntime.class.getDeclaredMethod(
|
||||
"cancelled", AgentRunRequest.class);
|
||||
method.setAccessible(true);
|
||||
AgentRuntimeEvent cancelledEvent = (AgentRuntimeEvent) method.invoke(runtime, request);
|
||||
List<AgentRuntimeEvent> events = List.of(cancelledEvent);
|
||||
Assert.assertTrue(events.stream().anyMatch(runtimeEvent -> runtimeEvent.getEventType() == AgentRuntimeEventType.CANCELLED));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldNotEmitFailedFromHook() {
|
||||
AgentRunRequest request = request();
|
||||
AgentScopeEventHook hook = new AgentScopeEventHook(request);
|
||||
ReActAgent agent = fakeRuntime().buildAgent(request, Sinks.many().unicast().onBackpressureBuffer());
|
||||
ErrorEvent errorEvent = new ErrorEvent(agent, new RuntimeException("boom"));
|
||||
|
||||
HookEvent returned = hook.onEvent(errorEvent).block();
|
||||
|
||||
Assert.assertSame(errorEvent, returned);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldAccumulateCompletedTextFromMessageDeltas() throws Exception {
|
||||
AgentRunRequest request = request();
|
||||
AgentScopeReActRuntime runtime = fakeRuntime();
|
||||
java.lang.reflect.Method method = AgentScopeReActRuntime.class.getDeclaredMethod(
|
||||
"updateFinalText", StringBuilder.class, AgentRuntimeEvent.class);
|
||||
method.setAccessible(true);
|
||||
StringBuilder builder = new StringBuilder();
|
||||
AgentRuntimeEvent first = AgentRuntimeEvent.of(AgentRuntimeEventType.MESSAGE_DELTA);
|
||||
first.getPayload().put("text", "hello ");
|
||||
AgentRuntimeEvent second = AgentRuntimeEvent.of(AgentRuntimeEventType.MESSAGE_DELTA);
|
||||
second.getPayload().put("text", "world");
|
||||
|
||||
method.invoke(runtime, builder, first);
|
||||
method.invoke(runtime, builder, second);
|
||||
|
||||
Assert.assertEquals("hello world", builder.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldAttachStructuredMessageOnCompletedEvent() throws Exception {
|
||||
AgentRunRequest request = request();
|
||||
AgentMessage message = AgentMessage.text(AgentMessageRole.ASSISTANT, "final answer");
|
||||
AgentScopeReActRuntime runtime = fakeRuntime();
|
||||
java.lang.reflect.Method method = AgentScopeReActRuntime.class.getDeclaredMethod(
|
||||
"completed", AgentRunRequest.class, String.class, AgentMessage.class);
|
||||
method.setAccessible(true);
|
||||
|
||||
AgentRuntimeEvent event = (AgentRuntimeEvent) method.invoke(runtime, request, "final answer", message);
|
||||
|
||||
Assert.assertEquals(AgentRuntimeEventType.COMPLETED, event.getEventType());
|
||||
Assert.assertEquals("final answer", event.getPayload().get("text"));
|
||||
Assert.assertSame(message, event.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldAttachKnowledgeReferencesToCompletedMessage() throws Exception {
|
||||
AgentRunRequest request = request();
|
||||
AgentMessage message = AgentMessage.text(AgentMessageRole.ASSISTANT, "final answer");
|
||||
AgentScopeReActRuntime runtime = fakeRuntime();
|
||||
java.lang.reflect.Method method = AgentScopeReActRuntime.class.getDeclaredMethod(
|
||||
"completed", AgentRunRequest.class, String.class, AgentMessage.class, Map.class);
|
||||
method.setAccessible(true);
|
||||
Map<String, AgentKnowledgeReference> refs = new java.util.LinkedHashMap<>();
|
||||
AgentKnowledgeReference ref = new AgentKnowledgeReference();
|
||||
ref.setKnowledgeId("kb-1");
|
||||
ref.setKnowledgeName("KB1");
|
||||
ref.setDocumentId("doc-1");
|
||||
ref.setDocumentName("doc-1.md");
|
||||
ref.setChunkId("chunk-1");
|
||||
refs.put("kb-1|doc-1|chunk-1", ref);
|
||||
|
||||
AgentRuntimeEvent event = (AgentRuntimeEvent) method.invoke(runtime, request, "final answer", message, refs);
|
||||
|
||||
Assert.assertEquals(AgentRuntimeEventType.COMPLETED, event.getEventType());
|
||||
List<AgentKnowledgeReference> knowledgeRefs = event.getMessage().getKnowledgeReferences();
|
||||
Assert.assertEquals(1, knowledgeRefs.size());
|
||||
Assert.assertEquals("doc-1.md", knowledgeRefs.get(0).getDocumentName());
|
||||
Assert.assertEquals("kb-1", knowledgeRefs.get(0).getKnowledgeId());
|
||||
}
|
||||
|
||||
private AgentScopeReActRuntime fakeRuntime() {
|
||||
return new AgentScopeReActRuntime(new FakeAgentScopeModelFactory(), new AgentScopeToolAdapter(),
|
||||
new AgentScopeKnowledgeAdapter(), new AgentScopeMemoryAdapter(), new AgentScopeSkillAdapter(),
|
||||
new AgentScopeMessageAdapter());
|
||||
}
|
||||
|
||||
private List<AgentRuntimeEvent> invokeToolAndCollect(AgentRunRequest request, int count) throws Exception {
|
||||
Sinks.Many<AgentRuntimeEvent> sink = Sinks.many().replay().all();
|
||||
ReActAgent agent = fakeRuntime().buildAgent(request, sink);
|
||||
CompletableFuture<List<AgentRuntimeEvent>> eventsFuture = sink.asFlux().take(count).collectList().toFuture();
|
||||
ToolUseBlock block = ToolUseBlock.builder()
|
||||
.id("call-hitl")
|
||||
.name("echo")
|
||||
.input(Map.of("text", "hello"))
|
||||
.build();
|
||||
agent.getToolkit().getTool("echo").callAsync(ToolCallParam.builder()
|
||||
.toolUseBlock(block)
|
||||
.input(Map.of("text", "hello"))
|
||||
.build())
|
||||
.block();
|
||||
return eventsFuture.get(3, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
private AgentRunRequest request() {
|
||||
AgentModelSpec modelSpec = new AgentModelSpec();
|
||||
modelSpec.setModelName("fake-model");
|
||||
AgentToolSpec tool = new AgentToolSpec();
|
||||
tool.setName("echo");
|
||||
tool.setDescription("Echo text");
|
||||
tool.setParametersSchema(Map.of("type", "object", "properties", Map.of("text", Map.of("type", "string"))));
|
||||
|
||||
AgentDefinition definition = new AgentDefinition();
|
||||
definition.setAgentId("agent-1");
|
||||
definition.setAgentName("agent-name");
|
||||
definition.setSystemPrompt("system");
|
||||
definition.setModelSpec(modelSpec);
|
||||
definition.getExecutionOptions().setMaxIters(3);
|
||||
definition.setToolSpecs(List.of(tool));
|
||||
definition.setKnowledgeSpecs(List.of(knowledge("kb-1", "KB1")));
|
||||
definition.setMemoryPolicy(AgentMemoryPolicy.inMemory());
|
||||
|
||||
AgentMemorySnapshot snapshot = new AgentMemorySnapshot();
|
||||
snapshot.addMessage(AgentMessage.text(AgentMessageRole.USER, "history"));
|
||||
|
||||
AgentRunRequest request = new AgentRunRequest();
|
||||
request.setRequestId("req-1");
|
||||
request.setTraceId("trace-1");
|
||||
request.setSessionId("session-1");
|
||||
request.setAgentDefinition(definition);
|
||||
request.setMemorySnapshot(snapshot);
|
||||
request.setUserMessage(AgentMessage.text(AgentMessageRole.USER, "hello"));
|
||||
request.getToolInvokers().put("echo", (arguments, context) -> AgentToolResult.success(String.valueOf(arguments.get("text"))));
|
||||
request.getKnowledgeRetrievers().put("kb-1", retrievalRequest -> AgentKnowledgeRetrievalResult.of(
|
||||
List.of(doc("doc-1", "chunk-1", "content-1", 0.8D))));
|
||||
return request;
|
||||
}
|
||||
|
||||
private AgentRunRequest requestWithSkillBoundTool() {
|
||||
AgentRunRequest request = request();
|
||||
AgentSkillSpec skill = new AgentSkillSpec();
|
||||
skill.setSkillId("skill-1");
|
||||
skill.setName("skill");
|
||||
skill.setDescription("desc");
|
||||
skill.setSkillContent("content");
|
||||
AgentSkillBoxSpec skillBoxSpec = new AgentSkillBoxSpec();
|
||||
skillBoxSpec.setSkillBoxId("box");
|
||||
skillBoxSpec.setSkills(List.of(skill));
|
||||
skillBoxSpec.setToolBindings(Map.of("skill-1", List.of("echo")));
|
||||
request.getAgentDefinition().setSkillBoxSpec(skillBoxSpec);
|
||||
return request;
|
||||
}
|
||||
|
||||
private com.easyagents.agent.runtime.skill.AgentSkillRuntimeContext activatedSkillContext(AgentRunRequest request) {
|
||||
com.easyagents.agent.runtime.skill.AgentSkillRuntimeContext skillContext =
|
||||
com.easyagents.agent.runtime.skill.AgentSkillRuntimeContext.from(request.getAgentDefinition().getSkillBoxSpec());
|
||||
skillContext.activateSkill("skill-1");
|
||||
return skillContext;
|
||||
}
|
||||
|
||||
private Object createModel(AgentModelProviderType providerType) {
|
||||
AgentModelSpec modelSpec = new AgentModelSpec();
|
||||
modelSpec.setProviderType(providerType);
|
||||
modelSpec.setModelName("test-model");
|
||||
modelSpec.setApiKey("test-key");
|
||||
return new AgentScopeModelFactory().create(modelSpec, new com.easyagents.agent.runtime.model.AgentGenerationOptions());
|
||||
}
|
||||
|
||||
private AgentKnowledgeSpec knowledge(String id, String name) {
|
||||
AgentKnowledgeSpec spec = new AgentKnowledgeSpec();
|
||||
spec.setKnowledgeId(id);
|
||||
spec.setName(name);
|
||||
spec.getMetadata().put("publishVersion", "v1");
|
||||
return spec;
|
||||
}
|
||||
|
||||
private AgentKnowledgeDocument doc(String documentId, String chunkId, String content, double score) {
|
||||
AgentKnowledgeDocument document = new AgentKnowledgeDocument();
|
||||
document.setDocumentId(documentId);
|
||||
document.setDocumentName(documentId + ".md");
|
||||
document.setChunkId(chunkId);
|
||||
document.setContent(content);
|
||||
document.setScore(score);
|
||||
document.getMetadata().put("sectionTitle", "section");
|
||||
return document;
|
||||
}
|
||||
|
||||
private static class TestState implements io.agentscope.core.state.State {
|
||||
private final String value;
|
||||
|
||||
private TestState(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package com.easyagents.agent.runtime.agentscope;
|
||||
|
||||
import com.easyagents.agent.runtime.model.AgentGenerationOptions;
|
||||
import com.easyagents.agent.runtime.model.AgentModelSpec;
|
||||
import io.agentscope.core.message.Msg;
|
||||
import io.agentscope.core.message.TextBlock;
|
||||
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 reactor.core.publisher.Flux;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 避免网络调用的测试模型工厂。
|
||||
*/
|
||||
class FakeAgentScopeModelFactory extends AgentScopeModelFactory {
|
||||
|
||||
@Override
|
||||
public Model create(AgentModelSpec modelSpec, AgentGenerationOptions generationOptions) {
|
||||
return new FakeModel(modelSpec == null ? "fake" : modelSpec.getModelName());
|
||||
}
|
||||
|
||||
private static class FakeModel implements Model {
|
||||
|
||||
private final String modelName;
|
||||
|
||||
private FakeModel(String modelName) {
|
||||
this.modelName = modelName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<ChatResponse> stream(List<Msg> messages, List<ToolSchema> toolSchemas, GenerateOptions options) {
|
||||
return Flux.just(ChatResponse.builder()
|
||||
.id("fake-response")
|
||||
.content(List.of(TextBlock.builder().text("fake answer").build()))
|
||||
.finishReason("stop")
|
||||
.build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getModelName() {
|
||||
return modelName;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user