refactor: 重构升级为有状态 Agent
- 完善 hook 对接机制 - 提供更加明确的调用方式
This commit is contained in:
@@ -0,0 +1,157 @@
|
||||
package com.easyagents.agent.runtime.agentscope;
|
||||
|
||||
import io.agentscope.core.message.*;
|
||||
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.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 面向 AutoContext 摘要调用的模型包装器。
|
||||
*/
|
||||
class AgentScopeAutoContextCompressionModel implements Model {
|
||||
|
||||
private final Model delegate;
|
||||
|
||||
/**
|
||||
* 创建 AutoContext 摘要模型包装器。
|
||||
*
|
||||
* @param delegate 实际调用的模型
|
||||
*/
|
||||
AgentScopeAutoContextCompressionModel(Model delegate) {
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
/**
|
||||
* 对 AutoContext 压缩摘要输入做协议安全化后转发给实际模型。
|
||||
*
|
||||
* @param messages AgentScope 消息
|
||||
* @param tools 工具声明
|
||||
* @param options 生成参数
|
||||
* @return 模型响应流
|
||||
*/
|
||||
@Override
|
||||
public Flux<ChatResponse> stream(List<Msg> messages, List<ToolSchema> tools, GenerateOptions options) {
|
||||
return delegate.stream(sanitizeCompressionMessages(messages), tools, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取模型名称。
|
||||
*
|
||||
* @return 模型名称
|
||||
*/
|
||||
@Override
|
||||
public String getModelName() {
|
||||
return delegate.getModelName();
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 AutoContext 摘要材料中的工具协议消息转换为普通文本消息。
|
||||
*
|
||||
* @param messages 原始摘要输入
|
||||
* @return 协议安全的摘要输入
|
||||
*/
|
||||
List<Msg> sanitizeCompressionMessages(List<Msg> messages) {
|
||||
if (messages == null || messages.isEmpty()) {
|
||||
return messages;
|
||||
}
|
||||
List<Msg> sanitized = new ArrayList<>(messages.size());
|
||||
for (Msg message : messages) {
|
||||
if (message == null) {
|
||||
continue;
|
||||
}
|
||||
if (requiresPlainText(message)) {
|
||||
sanitized.add(toPlainTextUserMessage(message));
|
||||
} else {
|
||||
sanitized.add(message);
|
||||
}
|
||||
}
|
||||
return sanitized;
|
||||
}
|
||||
|
||||
private boolean requiresPlainText(Msg message) {
|
||||
return message.getRole() == MsgRole.TOOL
|
||||
|| message.hasContentBlocks(ToolUseBlock.class)
|
||||
|| message.hasContentBlocks(ToolResultBlock.class);
|
||||
}
|
||||
|
||||
private Msg toPlainTextUserMessage(Msg message) {
|
||||
return Msg.builder()
|
||||
.id(message.getId())
|
||||
.role(MsgRole.USER)
|
||||
.name("context")
|
||||
.content(TextBlock.builder().text(renderMessage(message)).build())
|
||||
.metadata(message.getMetadata())
|
||||
.timestamp(message.getTimestamp())
|
||||
.build();
|
||||
}
|
||||
|
||||
private String renderMessage(Msg message) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.append("Context message role=").append(message.getRole());
|
||||
if (message.getName() != null && !message.getName().isBlank()) {
|
||||
builder.append(", name=").append(message.getName());
|
||||
}
|
||||
builder.append('\n');
|
||||
for (ContentBlock block : message.getContent()) {
|
||||
appendBlock(builder, block);
|
||||
}
|
||||
return builder.toString().trim();
|
||||
}
|
||||
|
||||
private void appendBlock(StringBuilder builder, ContentBlock block) {
|
||||
if (block instanceof TextBlock textBlock) {
|
||||
appendSection(builder, "text", textBlock.getText());
|
||||
return;
|
||||
}
|
||||
if (block instanceof ThinkingBlock thinkingBlock) {
|
||||
appendSection(builder, "thinking", thinkingBlock.getThinking());
|
||||
return;
|
||||
}
|
||||
if (block instanceof ToolUseBlock toolUseBlock) {
|
||||
appendSection(builder, "tool_use.id", toolUseBlock.getId());
|
||||
appendSection(builder, "tool_use.name", toolUseBlock.getName());
|
||||
appendSection(builder, "tool_use.input", renderMap(toolUseBlock.getInput()));
|
||||
return;
|
||||
}
|
||||
if (block instanceof ToolResultBlock toolResultBlock) {
|
||||
appendSection(builder, "tool_result.id", toolResultBlock.getId());
|
||||
appendSection(builder, "tool_result.name", toolResultBlock.getName());
|
||||
appendSection(builder, "tool_result.output", renderBlocks(toolResultBlock.getOutput()));
|
||||
return;
|
||||
}
|
||||
appendSection(builder, "content", String.valueOf(block));
|
||||
}
|
||||
|
||||
private String renderBlocks(List<ContentBlock> blocks) {
|
||||
if (blocks == null || blocks.isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
StringBuilder builder = new StringBuilder();
|
||||
for (ContentBlock block : blocks) {
|
||||
if (block instanceof TextBlock textBlock) {
|
||||
builder.append(textBlock.getText());
|
||||
} else {
|
||||
builder.append(String.valueOf(block));
|
||||
}
|
||||
builder.append('\n');
|
||||
}
|
||||
return builder.toString().trim();
|
||||
}
|
||||
|
||||
private String renderMap(Map<String, Object> map) {
|
||||
return map == null ? "{}" : map.toString();
|
||||
}
|
||||
|
||||
private void appendSection(StringBuilder builder, String label, String value) {
|
||||
if (value == null || value.isBlank()) {
|
||||
return;
|
||||
}
|
||||
builder.append(label).append(": ").append(value).append('\n');
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,8 @@
|
||||
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.AgentRuntimeExecutionContext;
|
||||
import com.easyagents.agent.runtime.event.*;
|
||||
import com.easyagents.agent.runtime.knowledge.*;
|
||||
import io.agentscope.core.message.TextBlock;
|
||||
import io.agentscope.core.rag.Knowledge;
|
||||
@@ -26,8 +25,8 @@ public class AgentScopeKnowledgeAdapter {
|
||||
* @param request 运行请求
|
||||
* @return 聚合 Knowledge;未配置知识库时返回 null
|
||||
*/
|
||||
public Knowledge createAggregateKnowledge(AgentRunRequest request) {
|
||||
return createAggregateKnowledge(request, null);
|
||||
public Knowledge createAggregateKnowledge(AgentRuntimeExecutionContext request) {
|
||||
return createAggregateKnowledge(request, (Sinks.Many<AgentRuntimeEvent>) null);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -37,11 +36,31 @@ public class AgentScopeKnowledgeAdapter {
|
||||
* @param eventSink 事件 sink
|
||||
* @return 聚合 Knowledge;未配置知识库时返回 null
|
||||
*/
|
||||
public Knowledge createAggregateKnowledge(AgentRunRequest request, Sinks.Many<AgentRuntimeEvent> eventSink) {
|
||||
public Knowledge createAggregateKnowledge(AgentRuntimeExecutionContext request, Sinks.Many<AgentRuntimeEvent> eventSink) {
|
||||
return createAggregateKnowledge(request, fixedHolder(request, eventSink));
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建可读取当前运行轮次事件出口的聚合 Knowledge。
|
||||
*
|
||||
* @param request 运行时级上下文
|
||||
* @param turnContextHolder 当前运行轮次上下文持有器
|
||||
* @return 聚合 Knowledge;未配置知识库时返回 null
|
||||
*/
|
||||
public Knowledge createAggregateKnowledge(AgentRuntimeExecutionContext request,
|
||||
AgentRuntimeTurnContextHolder turnContextHolder) {
|
||||
if (request.getAgentDefinition().getKnowledgeSpecs().isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return new AggregateKnowledge(request, eventSink);
|
||||
return new AggregateKnowledge(request, turnContextHolder);
|
||||
}
|
||||
|
||||
private AgentRuntimeTurnContextHolder fixedHolder(AgentRuntimeExecutionContext request,
|
||||
Sinks.Many<AgentRuntimeEvent> eventSink) {
|
||||
AgentRuntimeTurnContextHolder holder = new AgentRuntimeTurnContextHolder();
|
||||
AgentRuntimeEventBridge bridge = new AgentRuntimeEventBridge(request, holder);
|
||||
holder.set(new AgentRuntimeTurnContext(null, eventSink, bridge));
|
||||
return holder;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -71,9 +90,9 @@ public class AgentScopeKnowledgeAdapter {
|
||||
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())
|
||||
.content(TextBlock.builder().text(safeContent(document)).build())
|
||||
.docId(safeDocumentId(document))
|
||||
.chunkId(safeChunkId(document))
|
||||
.payload(payload)
|
||||
.build();
|
||||
Document converted = new Document(metadata);
|
||||
@@ -81,17 +100,59 @@ public class AgentScopeKnowledgeAdapter {
|
||||
return converted;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 AgentScope 要求的非空文档 ID。
|
||||
*
|
||||
* @param document 知识文档
|
||||
* @return 非空文档 ID
|
||||
*/
|
||||
private String safeDocumentId(AgentKnowledgeDocument document) {
|
||||
if (document.getDocumentId() != null && !document.getDocumentId().isBlank()) {
|
||||
return document.getDocumentId();
|
||||
}
|
||||
if (document.getChunkId() != null && !document.getChunkId().isBlank()) {
|
||||
return document.getChunkId();
|
||||
}
|
||||
return "knowledge-document";
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 AgentScope 要求的非空分片 ID。
|
||||
*
|
||||
* @param document 知识文档
|
||||
* @return 非空分片 ID
|
||||
*/
|
||||
private String safeChunkId(AgentKnowledgeDocument document) {
|
||||
if (document.getChunkId() != null && !document.getChunkId().isBlank()) {
|
||||
return document.getChunkId();
|
||||
}
|
||||
if (document.getDocumentId() != null && !document.getDocumentId().isBlank()) {
|
||||
return document.getDocumentId();
|
||||
}
|
||||
return "0";
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 AgentScope 要求的非空文档内容。
|
||||
*
|
||||
* @param document 知识文档
|
||||
* @return 文档内容
|
||||
*/
|
||||
private String safeContent(AgentKnowledgeDocument document) {
|
||||
return document.getContent() == null ? "" : document.getContent();
|
||||
}
|
||||
|
||||
/**
|
||||
* 将检索调用分发到多个知识源的聚合 Knowledge 实现。
|
||||
*/
|
||||
private class AggregateKnowledge implements Knowledge {
|
||||
|
||||
private final AgentRunRequest request;
|
||||
private final Sinks.Many<AgentRuntimeEvent> eventSink;
|
||||
private final AgentRuntimeExecutionContext request;
|
||||
private final AgentRuntimeTurnContextHolder turnContextHolder;
|
||||
|
||||
private AggregateKnowledge(AgentRunRequest request, Sinks.Many<AgentRuntimeEvent> eventSink) {
|
||||
private AggregateKnowledge(AgentRuntimeExecutionContext request, AgentRuntimeTurnContextHolder turnContextHolder) {
|
||||
this.request = request;
|
||||
this.eventSink = eventSink;
|
||||
this.turnContextHolder = turnContextHolder;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -139,9 +200,10 @@ public class AgentScopeKnowledgeAdapter {
|
||||
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());
|
||||
AgentRuntimeExecutionContext currentRequest = currentRequest();
|
||||
retrievalRequest.setRuntimeContext(currentRequest.getRuntimeContext());
|
||||
retrievalRequest.getMetadata().put("traceId", currentRequest.getTraceId());
|
||||
retrievalRequest.getMetadata().put("sessionId", currentRequest.getSessionId());
|
||||
AgentKnowledgeRetrievalResult result = retriever.retrieve(retrievalRequest);
|
||||
if (result == null || result.getDocuments() == null) {
|
||||
emitKnowledgeRetrievalEvent(query, spec, retrievalRequest, new ArrayList<>());
|
||||
@@ -181,7 +243,11 @@ public class AgentScopeKnowledgeAdapter {
|
||||
}
|
||||
|
||||
/**
|
||||
* 发射知识库检索事件,供聊天界面展示检索过程。
|
||||
* 发射知识库检索旁路事件,供聊天界面展示检索过程。
|
||||
*
|
||||
* <p>知识库检索本身属于 AgentScope RAG 主线路,返回的 Document 会继续进入
|
||||
* AgentScope 的上下文注入流程;这里发出的 {@code KNOWLEDGE_RETRIEVAL}
|
||||
* 只是旁路告知调用方,不会回写 memory,也不会参与模型消息序列。</p>
|
||||
*
|
||||
* @param query 查询
|
||||
* @param spec 知识库声明
|
||||
@@ -192,32 +258,35 @@ public class AgentScopeKnowledgeAdapter {
|
||||
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());
|
||||
AgentRuntimeEvent event = currentEventBridge().event(AgentRuntimeEventType.KNOWLEDGE_RETRIEVAL);
|
||||
event.getPayload().put("query", query);
|
||||
event.getPayload().put("knowledgeId", spec.getKnowledgeId());
|
||||
event.getPayload().put("knowledgeName", spec.getName());
|
||||
event.getPayload().put("knowledgeType", spec.getMetadata().get("knowledgeType"));
|
||||
event.getPayload().put("faqCollection", spec.getMetadata().get("faqCollection"));
|
||||
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);
|
||||
currentEventBridge().emit(event);
|
||||
}
|
||||
|
||||
private AgentRuntimeExecutionContext currentRequest() {
|
||||
return turnContextHolder == null ? request : turnContextHolder.executionContext(request);
|
||||
}
|
||||
|
||||
private AgentRuntimeEventBridge currentEventBridge() {
|
||||
if (turnContextHolder != null && turnContextHolder.eventBridge().isPresent()) {
|
||||
return turnContextHolder.eventBridge().get();
|
||||
}
|
||||
return new AgentRuntimeEventBridge(request, turnContextHolder);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建用于事件展示的文档摘要,避免把全文内容放入事件。
|
||||
* 构建用于事件展示的命中片段,保留前端引注需要的原始 chunk 内容。
|
||||
*
|
||||
* @param documents 检索文档
|
||||
* @return 文档摘要列表
|
||||
* @return 命中片段列表
|
||||
*/
|
||||
private List<Map<String, Object>> documentSummaries(List<AgentKnowledgeDocument> documents) {
|
||||
List<Map<String, Object>> summaries = new ArrayList<>();
|
||||
@@ -229,6 +298,7 @@ public class AgentScopeKnowledgeAdapter {
|
||||
summary.put("documentId", document.getDocumentId());
|
||||
summary.put("documentName", document.getDocumentName());
|
||||
summary.put("chunkId", document.getChunkId());
|
||||
summary.put("chunkContent", document.getContent());
|
||||
summary.put("score", document.getScore());
|
||||
summary.put("sourceUri", document.getSourceUri());
|
||||
summary.put("metadata", document.getMetadata());
|
||||
|
||||
@@ -28,16 +28,35 @@ public class AgentScopeMemoryAdapter {
|
||||
* @return AgentScope 记忆
|
||||
*/
|
||||
public Memory createMemory(AgentMemorySnapshot snapshot, AgentMemoryPolicy policy, Model model) {
|
||||
return createMemoryResult(snapshot, policy, model).getMemory();
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建记忆并返回 AutoContext 配置快照。
|
||||
*
|
||||
* <p>有状态 runtime 需要把同一份 {@link AutoContextConfig} 交给 AutoContext干预器,
|
||||
* 用于判断是否进入压缩流程并发出旁路事件。非 AutoContext 记忆不会返回配置。</p>
|
||||
*
|
||||
* @param snapshot 记忆快照
|
||||
* @param policy 记忆策略
|
||||
* @param model 用于自动压缩的模型
|
||||
* @return 记忆构建结果
|
||||
*/
|
||||
public AgentScopeMemoryBuildResult createMemoryResult(AgentMemorySnapshot snapshot,
|
||||
AgentMemoryPolicy policy,
|
||||
Model model) {
|
||||
AgentMemoryPolicy safePolicy = policy == null ? AgentMemoryPolicy.autoContext() : policy;
|
||||
Memory memory;
|
||||
AutoContextConfig autoContextConfig = null;
|
||||
if (safePolicy.getType() == com.easyagents.agent.runtime.memory.AgentMemoryType.AUTO_CONTEXT
|
||||
&& safePolicy.getCompressionParameter().isEnabled()) {
|
||||
memory = new AutoContextMemory(toAutoContextConfig(safePolicy.getCompressionParameter()), model);
|
||||
autoContextConfig = toAutoContextConfig(safePolicy.getCompressionParameter());
|
||||
memory = new AutoContextMemory(autoContextConfig, new AgentScopeAutoContextCompressionModel(model));
|
||||
} else {
|
||||
memory = new InMemoryMemory();
|
||||
}
|
||||
attachMessages(memory, snapshot, safePolicy.getMaxAttachedMessageCount());
|
||||
return memory;
|
||||
return new AgentScopeMemoryBuildResult(memory, autoContextConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -48,13 +67,14 @@ public class AgentScopeMemoryAdapter {
|
||||
*/
|
||||
public AutoContextConfig toAutoContextConfig(AgentMemoryCompressionParameter parameter) {
|
||||
AgentMemoryCompressionParameter safeParameter = parameter == null ? new AgentMemoryCompressionParameter() : parameter;
|
||||
Integer compressionThreshold = safeParameter.getMinCompressionTokenThreshold();
|
||||
return AutoContextConfig.builder()
|
||||
.msgThreshold(safeParameter.getMsgThreshold())
|
||||
.msgThreshold(safeParameter.getMsgThreshold() == null ? Integer.MAX_VALUE : safeParameter.getMsgThreshold())
|
||||
.lastKeep(safeParameter.getLastKeep())
|
||||
.tokenRatio(safeParameter.getTokenRatio())
|
||||
.maxToken(safeParameter.getMaxToken())
|
||||
.tokenRatio(1.0D)
|
||||
.maxToken(compressionThreshold == null ? Long.MAX_VALUE : compressionThreshold)
|
||||
.largePayloadThreshold(safeParameter.getLargePayloadThreshold())
|
||||
.minCompressionTokenThreshold(safeParameter.getMinCompressionTokenThreshold())
|
||||
.minCompressionTokenThreshold(compressionThreshold == null ? Integer.MAX_VALUE : compressionThreshold)
|
||||
.currentRoundCompressionRatio(safeParameter.getCurrentRoundCompressionRatio())
|
||||
.minConsecutiveToolMessages(safeParameter.getMinConsecutiveToolMessages())
|
||||
.build();
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
package com.easyagents.agent.runtime.agentscope;
|
||||
|
||||
import io.agentscope.core.memory.Memory;
|
||||
import io.agentscope.core.memory.autocontext.AutoContextConfig;
|
||||
|
||||
/**
|
||||
* AgentScope 记忆构建结果。
|
||||
*
|
||||
* <p>AutoContext 的压缩入口判断必须使用创建 {@code AutoContextMemory} 时的同一份
|
||||
* {@link AutoContextConfig}。该结果对象将记忆实例和配置快照一起返回,避免运行时
|
||||
* 干预器重新推导配置导致事件触发条件与 AgentScope 主线路不一致。</p>
|
||||
*/
|
||||
public class AgentScopeMemoryBuildResult {
|
||||
|
||||
private final Memory memory;
|
||||
private final AutoContextConfig autoContextConfig;
|
||||
|
||||
/**
|
||||
* 创建记忆构建结果。
|
||||
*
|
||||
* @param memory AgentScope 记忆实例
|
||||
* @param autoContextConfig AutoContext 配置,非 AutoContext 记忆时为 null
|
||||
*/
|
||||
public AgentScopeMemoryBuildResult(Memory memory, AutoContextConfig autoContextConfig) {
|
||||
this.memory = memory;
|
||||
this.autoContextConfig = autoContextConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 AgentScope 记忆实例。
|
||||
*
|
||||
* @return AgentScope 记忆实例
|
||||
*/
|
||||
public Memory getMemory() {
|
||||
return memory;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 AutoContext 配置。
|
||||
*
|
||||
* @return AutoContext 配置,非 AutoContext 记忆时为 null
|
||||
*/
|
||||
public AutoContextConfig getAutoContextConfig() {
|
||||
return autoContextConfig;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,37 @@
|
||||
package com.easyagents.agent.runtime.agentscope;
|
||||
|
||||
import com.easyagents.agent.runtime.event.AgentRuntimeObservationManager;
|
||||
import io.agentscope.core.hook.Hook;
|
||||
import io.agentscope.core.hook.HookEvent;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
/**
|
||||
* AgentScope Hook 的统一入口。
|
||||
*/
|
||||
public class AgentScopeRuntimeHook implements Hook {
|
||||
|
||||
private final AgentRuntimeObservationManager observationManager;
|
||||
|
||||
/**
|
||||
* 创建统一 Hook 入口。
|
||||
*
|
||||
* @param observationManager 观察和干预调度器
|
||||
*/
|
||||
public AgentScopeRuntimeHook(AgentRuntimeObservationManager observationManager) {
|
||||
this.observationManager = observationManager == null
|
||||
? AgentRuntimeObservationManager.empty()
|
||||
: observationManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理 AgentScope Hook 事件。
|
||||
*
|
||||
* @param event Hook 事件
|
||||
* @param <T> Hook 事件类型
|
||||
* @return 处理后的 Hook 事件
|
||||
*/
|
||||
@Override
|
||||
public <T extends HookEvent> Mono<T> onEvent(T event) {
|
||||
return observationManager.handle(event);
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,12 @@
|
||||
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 com.easyagents.agent.runtime.persistence.session.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;
|
||||
@@ -32,7 +30,7 @@ public class AgentScopeSessionAdapter implements Session {
|
||||
|
||||
@Override
|
||||
public void save(SessionKey sessionKey, String name, State state) {
|
||||
sessionStore.save(toKey(sessionKey), name, AgentRuntimeState.of(name, state));
|
||||
sessionStore.save(toKey(sessionKey), name, state);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -44,13 +42,7 @@ public class AgentScopeSessionAdapter implements Session {
|
||||
*/
|
||||
@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);
|
||||
sessionStore.saveList(toKey(sessionKey), name, states);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -64,10 +56,7 @@ public class AgentScopeSessionAdapter implements Session {
|
||||
*/
|
||||
@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);
|
||||
return sessionStore.get(toKey(sessionKey), name, clazz);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -81,11 +70,7 @@ public class AgentScopeSessionAdapter implements Session {
|
||||
*/
|
||||
@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());
|
||||
return sessionStore.getList(toKey(sessionKey), name, clazz);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -129,7 +114,7 @@ public class AgentScopeSessionAdapter implements Session {
|
||||
*/
|
||||
public static StatePersistence toStatePersistence(AgentPersistencePolicy policy) {
|
||||
if (policy == null || !policy.isEnabled()) {
|
||||
return StatePersistence.none();
|
||||
return StatePersistence.memoryOnly();
|
||||
}
|
||||
return StatePersistence.builder()
|
||||
.memoryManaged(policy.isMemoryManaged())
|
||||
@@ -154,24 +139,17 @@ public class AgentScopeSessionAdapter implements Session {
|
||||
}
|
||||
|
||||
/**
|
||||
* 运行时支撑的 AgentScope 会话键。
|
||||
*/
|
||||
private static class RuntimeSessionKey implements SessionKey {
|
||||
|
||||
private final String value;
|
||||
|
||||
private RuntimeSessionKey(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
* 运行时支撑的 AgentScope 会话键。
|
||||
*/
|
||||
private record RuntimeSessionKey(String value) implements SessionKey {
|
||||
/**
|
||||
* 将键转换为稳定标识符。
|
||||
* 将键转换为稳定标识符。
|
||||
*
|
||||
* @return 标识符
|
||||
*/
|
||||
@Override
|
||||
public String toIdentifier() {
|
||||
return value;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.easyagents.agent.runtime.agentscope;
|
||||
|
||||
import com.easyagents.agent.runtime.AgentRuntimeException;
|
||||
import com.easyagents.agent.runtime.skill.AgentSkillBoxSpec;
|
||||
import com.easyagents.agent.runtime.skill.AgentSkillCompiler;
|
||||
import com.easyagents.agent.runtime.skill.AgentSkillSpec;
|
||||
@@ -8,7 +9,6 @@ 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;
|
||||
|
||||
@@ -19,18 +19,14 @@ 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();
|
||||
validateSkillSpec(skillSpec);
|
||||
return new EasyAgentsAgentSkill(
|
||||
skillSpec.getSkillId(),
|
||||
skillSpec.getName(),
|
||||
skillSpec.getDescription(),
|
||||
skillSpec.getSkillContent(),
|
||||
skillSpec.getResources(),
|
||||
skillSpec.getSource());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -105,4 +101,71 @@ public class AgentScopeSkillAdapter implements AgentSkillCompiler<AgentSkill> {
|
||||
skillBox.syncToolGroupStates();
|
||||
return skillBox;
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验 Skill 声明是否具备 AgentScope 注册和模型提示所需的必要信息。
|
||||
*
|
||||
* @param skillSpec Skill 声明
|
||||
* @throws AgentRuntimeException Skill 声明为空或核心字段缺失时抛出
|
||||
*/
|
||||
private void validateSkillSpec(AgentSkillSpec skillSpec) {
|
||||
if (skillSpec == null) {
|
||||
throw new AgentRuntimeException("Agent skill spec is required.");
|
||||
}
|
||||
if (skillSpec.getSkillId() == null || skillSpec.getSkillId().isBlank()) {
|
||||
throw new AgentRuntimeException("Agent skill id is required.");
|
||||
}
|
||||
if (skillSpec.getName() == null || skillSpec.getName().isBlank()) {
|
||||
throw new AgentRuntimeException("Agent skill name is required: " + skillSpec.getSkillId());
|
||||
}
|
||||
if (skillSpec.getDescription() == null || skillSpec.getDescription().isBlank()) {
|
||||
throw new AgentRuntimeException("Agent skill description is required: " + skillSpec.getSkillId());
|
||||
}
|
||||
if (skillSpec.getSkillContent() == null || skillSpec.getSkillContent().isBlank()) {
|
||||
throw new AgentRuntimeException("Agent skill content is required: " + skillSpec.getSkillId());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 保持 Easy-Agents Skill ID 与 AgentScope Skill ID 完全一致。
|
||||
*
|
||||
* <p>AgentScope 默认用 {@code name + "_" + source} 生成 Skill ID,而 Easy-Agents
|
||||
* 的工具绑定、旁路事件和调用层配置都以 {@link AgentSkillSpec#getSkillId()} 为准。
|
||||
* 如果不覆盖这里,模型在 prompt 中看到的 skill-id 会和 Easy 侧绑定 key 不一致,
|
||||
* 后续 Skill 状态监听也无法做到精准归属。</p>
|
||||
*/
|
||||
private static class EasyAgentsAgentSkill extends AgentSkill {
|
||||
|
||||
private final String skillId;
|
||||
|
||||
/**
|
||||
* 创建 ID 对齐的 AgentScope Skill。
|
||||
*
|
||||
* @param skillId Easy-Agents Skill ID
|
||||
* @param name Skill 展示名称
|
||||
* @param description Skill 描述
|
||||
* @param skillContent Skill 内容
|
||||
* @param resources Skill 资源
|
||||
* @param source Skill 来源
|
||||
*/
|
||||
EasyAgentsAgentSkill(String skillId,
|
||||
String name,
|
||||
String description,
|
||||
String skillContent,
|
||||
Map<String, String> resources,
|
||||
String source) {
|
||||
super(name, description, skillContent, resources, source);
|
||||
this.skillId = skillId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 Easy-Agents 声明的 Skill ID。
|
||||
*
|
||||
* @return Easy-Agents Skill ID
|
||||
*/
|
||||
@Override
|
||||
public String getSkillId() {
|
||||
return skillId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
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.AgentRuntimeExecutionContext;
|
||||
import com.easyagents.agent.runtime.event.*;
|
||||
import com.easyagents.agent.runtime.hitl.AgentPendingState;
|
||||
import com.easyagents.agent.runtime.hitl.AgentToolApprovalCoordinator;
|
||||
import com.easyagents.agent.runtime.hitl.AgentToolApprovalRejectedException;
|
||||
@@ -39,8 +38,9 @@ public class AgentScopeToolAdapter {
|
||||
* @param request 运行请求
|
||||
* @return AgentScope 工具
|
||||
*/
|
||||
public AgentTool adapt(AgentToolSpec toolSpec, AgentToolInvoker invoker, AgentRunRequest request) {
|
||||
return adapt(toolSpec, invoker, request, AgentToolApprovalCoordinator.disabled(), null);
|
||||
public AgentTool adapt(AgentToolSpec toolSpec, AgentToolInvoker invoker, AgentRuntimeExecutionContext request) {
|
||||
return adapt(toolSpec, invoker, request, AgentToolApprovalCoordinator.disabled(),
|
||||
(Sinks.Many<AgentRuntimeEvent>) null);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -54,9 +54,10 @@ public class AgentScopeToolAdapter {
|
||||
*/
|
||||
public AgentTool adapt(AgentToolSpec toolSpec,
|
||||
AgentToolInvoker invoker,
|
||||
AgentRunRequest request,
|
||||
AgentRuntimeExecutionContext request,
|
||||
Sinks.Many<AgentRuntimeEvent> eventSink) {
|
||||
return adapt(toolSpec, invoker, request, AgentToolApprovalCoordinator.disabled(), eventSink);
|
||||
return adapt(toolSpec, invoker, request, AgentToolApprovalCoordinator.disabled(), fixedHolder(request, eventSink),
|
||||
null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -71,10 +72,10 @@ public class AgentScopeToolAdapter {
|
||||
*/
|
||||
public AgentTool adapt(AgentToolSpec toolSpec,
|
||||
AgentToolInvoker invoker,
|
||||
AgentRunRequest request,
|
||||
AgentRuntimeExecutionContext request,
|
||||
AgentToolApprovalCoordinator approvalCoordinator,
|
||||
Sinks.Many<AgentRuntimeEvent> eventSink) {
|
||||
return adapt(toolSpec, invoker, request, approvalCoordinator, eventSink, null);
|
||||
return adapt(toolSpec, invoker, request, approvalCoordinator, fixedHolder(request, eventSink), null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -90,11 +91,11 @@ public class AgentScopeToolAdapter {
|
||||
*/
|
||||
public AgentTool adapt(AgentToolSpec toolSpec,
|
||||
AgentToolInvoker invoker,
|
||||
AgentRunRequest request,
|
||||
AgentRuntimeExecutionContext request,
|
||||
AgentToolApprovalCoordinator approvalCoordinator,
|
||||
Sinks.Many<AgentRuntimeEvent> eventSink,
|
||||
AgentSkillBinding skillBinding) {
|
||||
return adapt(toolSpec, invoker, request, approvalCoordinator, eventSink, null, skillBinding);
|
||||
return adapt(toolSpec, invoker, request, approvalCoordinator, fixedHolder(request, eventSink), null, skillBinding);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -111,27 +112,153 @@ public class AgentScopeToolAdapter {
|
||||
*/
|
||||
public AgentTool adapt(AgentToolSpec toolSpec,
|
||||
AgentToolInvoker invoker,
|
||||
AgentRunRequest request,
|
||||
AgentRuntimeExecutionContext request,
|
||||
AgentToolApprovalCoordinator approvalCoordinator,
|
||||
Sinks.Many<AgentRuntimeEvent> eventSink,
|
||||
AgentSkillRuntimeContext skillContext,
|
||||
AgentSkillBinding skillBinding) {
|
||||
return adapt(toolSpec, invoker, request, approvalCoordinator, fixedHolder(request, eventSink), skillContext, skillBinding);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将运行时工具声明和调用器转换为可读取当前运行轮次的 AgentScope AgentTool。
|
||||
*
|
||||
* @param toolSpec 工具声明
|
||||
* @param invoker 工具调用器
|
||||
* @param request 运行时级上下文
|
||||
* @param approvalCoordinator 审批协调器
|
||||
* @param turnContextHolder 当前运行轮次上下文持有器
|
||||
* @param skillContext Skill 运行时上下文
|
||||
* @param skillBinding Skill 静态绑定关系
|
||||
* @return AgentScope 工具
|
||||
*/
|
||||
public AgentTool adapt(AgentToolSpec toolSpec,
|
||||
AgentToolInvoker invoker,
|
||||
AgentRuntimeExecutionContext request,
|
||||
AgentToolApprovalCoordinator approvalCoordinator,
|
||||
AgentRuntimeTurnContextHolder turnContextHolder,
|
||||
AgentSkillRuntimeContext skillContext,
|
||||
AgentSkillBinding skillBinding) {
|
||||
return adapt(toolSpec, invoker, request, approvalCoordinator, turnContextHolder, skillContext, skillBinding, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将运行时工具声明和调用器转换为可读取当前运行轮次的 AgentScope AgentTool。
|
||||
*
|
||||
* @param toolSpec 工具声明
|
||||
* @param invoker 工具调用器
|
||||
* @param request 运行时级上下文
|
||||
* @param approvalCoordinator 审批协调器
|
||||
* @param turnContextHolder 当前运行轮次上下文持有器
|
||||
* @param skillContext Skill 运行时上下文
|
||||
* @param skillBinding Skill 静态绑定关系
|
||||
* @param emitNormalToolResult 是否由 adapter 发出普通工具结果旁路事件
|
||||
* @return AgentScope 工具
|
||||
*/
|
||||
public AgentTool adapt(AgentToolSpec toolSpec,
|
||||
AgentToolInvoker invoker,
|
||||
AgentRuntimeExecutionContext request,
|
||||
AgentToolApprovalCoordinator approvalCoordinator,
|
||||
AgentRuntimeTurnContextHolder turnContextHolder,
|
||||
AgentSkillRuntimeContext skillContext,
|
||||
AgentSkillBinding skillBinding,
|
||||
boolean emitNormalToolResult) {
|
||||
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);
|
||||
return new RuntimeAgentTool(toolSpec, invoker, request, approvalCoordinator, turnContextHolder,
|
||||
skillContext, skillBinding, emitNormalToolResult, true, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将运行时工具声明和调用器转换为可读取当前运行轮次的 AgentScope AgentTool。
|
||||
*
|
||||
* @param toolSpec 工具声明
|
||||
* @param invoker 工具调用器
|
||||
* @param request 运行时级上下文
|
||||
* @param approvalCoordinator 审批协调器
|
||||
* @param turnContextHolder 当前运行轮次上下文持有器
|
||||
* @param skillContext Skill 运行时上下文
|
||||
* @param skillBinding Skill 静态绑定关系
|
||||
* @param emitNormalToolResult 是否由 adapter 发出普通工具结果旁路事件
|
||||
* @param emitSkillStep 是否由 adapter 发出 Skill 步骤旁路事件
|
||||
* @return AgentScope 工具
|
||||
*/
|
||||
public AgentTool adapt(AgentToolSpec toolSpec,
|
||||
AgentToolInvoker invoker,
|
||||
AgentRuntimeExecutionContext request,
|
||||
AgentToolApprovalCoordinator approvalCoordinator,
|
||||
AgentRuntimeTurnContextHolder turnContextHolder,
|
||||
AgentSkillRuntimeContext skillContext,
|
||||
AgentSkillBinding skillBinding,
|
||||
boolean emitNormalToolResult,
|
||||
boolean emitSkillStep) {
|
||||
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, turnContextHolder,
|
||||
skillContext, skillBinding, emitNormalToolResult, emitSkillStep, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将运行时工具声明和调用器转换为可读取当前运行轮次的 AgentScope AgentTool。
|
||||
*
|
||||
* @param toolSpec 工具声明
|
||||
* @param invoker 工具调用器
|
||||
* @param request 运行时级上下文
|
||||
* @param approvalCoordinator 审批协调器
|
||||
* @param turnContextHolder 当前运行轮次上下文持有器
|
||||
* @param skillContext Skill 运行时上下文
|
||||
* @param skillBinding Skill 静态绑定关系
|
||||
* @param emitNormalToolResult 是否由 adapter 发出普通工具结果旁路事件
|
||||
* @param emitSkillStep 是否由 adapter 发出 Skill 步骤旁路事件
|
||||
* @param handleApprovalInTool 是否在工具执行阶段处理审批
|
||||
* @return AgentScope 工具
|
||||
*/
|
||||
public AgentTool adapt(AgentToolSpec toolSpec,
|
||||
AgentToolInvoker invoker,
|
||||
AgentRuntimeExecutionContext request,
|
||||
AgentToolApprovalCoordinator approvalCoordinator,
|
||||
AgentRuntimeTurnContextHolder turnContextHolder,
|
||||
AgentSkillRuntimeContext skillContext,
|
||||
AgentSkillBinding skillBinding,
|
||||
boolean emitNormalToolResult,
|
||||
boolean emitSkillStep,
|
||||
boolean handleApprovalInTool) {
|
||||
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, turnContextHolder,
|
||||
skillContext, skillBinding, emitNormalToolResult, emitSkillStep, handleApprovalInTool);
|
||||
}
|
||||
|
||||
private AgentRuntimeTurnContextHolder fixedHolder(AgentRuntimeExecutionContext request,
|
||||
Sinks.Many<AgentRuntimeEvent> eventSink) {
|
||||
AgentRuntimeTurnContextHolder holder = new AgentRuntimeTurnContextHolder();
|
||||
AgentRuntimeEventBridge bridge = new AgentRuntimeEventBridge(request, holder);
|
||||
holder.set(new AgentRuntimeTurnContext(null, eventSink, bridge));
|
||||
return holder;
|
||||
}
|
||||
|
||||
private record RuntimeAgentTool(AgentToolSpec toolSpec,
|
||||
AgentToolInvoker invoker,
|
||||
AgentRunRequest request,
|
||||
AgentRuntimeExecutionContext request,
|
||||
AgentToolApprovalCoordinator approvalCoordinator,
|
||||
Sinks.Many<AgentRuntimeEvent> eventSink,
|
||||
AgentRuntimeTurnContextHolder turnContextHolder,
|
||||
AgentSkillRuntimeContext skillContext,
|
||||
AgentSkillBinding skillBinding) implements AgentTool {
|
||||
AgentSkillBinding skillBinding,
|
||||
boolean emitNormalToolResult,
|
||||
boolean emitSkillStep,
|
||||
boolean handleApprovalInTool) implements AgentTool {
|
||||
|
||||
/**
|
||||
* 获取工具名称。
|
||||
@@ -181,11 +308,14 @@ public class AgentScopeToolAdapter {
|
||||
*/
|
||||
@Override
|
||||
public Mono<ToolResultBlock> callAsync(ToolCallParam param) {
|
||||
emit(toolCallEvent(param == null ? null : param.getToolUseBlock()));
|
||||
AgentRuntimeEvent startEvent = toolExecutionStartEvent(param == null ? null : param.getToolUseBlock());
|
||||
if (startEvent != null) {
|
||||
emit(startEvent);
|
||||
}
|
||||
Map<String, Object> input = param == null || param.getInput() == null
|
||||
? new LinkedHashMap<>()
|
||||
: new LinkedHashMap<>(param.getInput());
|
||||
if (toolSpec.isApprovalRequired()) {
|
||||
if (handleApprovalInTool && toolSpec.isApprovalRequired()) {
|
||||
if (approvalCoordinator == null) {
|
||||
throw new AgentRuntimeException("Agent tool approval coordinator is required: " + toolSpec.getName());
|
||||
}
|
||||
@@ -224,12 +354,13 @@ public class AgentScopeToolAdapter {
|
||||
* @return 工具上下文
|
||||
*/
|
||||
private AgentToolContext buildContext(ToolCallParam param) {
|
||||
AgentRuntimeExecutionContext currentRequest = currentRequest();
|
||||
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());
|
||||
context.setRequestId(currentRequest.getRequestId());
|
||||
context.setTraceId(currentRequest.getTraceId());
|
||||
context.setSessionId(currentRequest.getSessionId());
|
||||
context.setAgentId(currentRequest.getAgentDefinition().getAgentId());
|
||||
context.setRuntimeContext(currentRequest.getRuntimeContext());
|
||||
if (param != null && param.getToolUseBlock() != null) {
|
||||
context.setToolCallId(param.getToolUseBlock().getId());
|
||||
}
|
||||
@@ -273,35 +404,39 @@ public class AgentScopeToolAdapter {
|
||||
AgentToolContext context = buildContext(param);
|
||||
AgentToolResult result = invoker.invoke(input, context);
|
||||
ToolResultBlock block = toToolResultBlock(param, result);
|
||||
emit(toolResultEvent(block));
|
||||
// 有状态 runtime 中,普通工具结果由 AgentScope 原生 PostActingEvent
|
||||
// 旁路观察器统一发出;旧 sink 辅助路径没有统一 hook,因此仍允许 adapter 兼容发射。
|
||||
if (emitNormalToolResult || (emitSkillStep && activeSkillBinding() != null)) {
|
||||
emit(toolResultEvent(block));
|
||||
}
|
||||
return block;
|
||||
}
|
||||
|
||||
/**
|
||||
* 存在 sink 时发射一条事件。
|
||||
* 通过旁路事件桥发射事件。
|
||||
*
|
||||
* <p>这里发射的是 Easy-Agents 对调用方的监察/交互事件,不是 AgentScope
|
||||
* 主线路消息。主线路的 tool_call/tool_result 顺序仍由 AgentScope 自己维护。</p>
|
||||
*
|
||||
* @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);
|
||||
}
|
||||
}
|
||||
currentEventBridge().emit(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建工具调用事件。
|
||||
* 构建工具执行开始事件。
|
||||
* 普通工具调用由 AgentScope stream 的 ToolUseBlock 表达,这里仅为已激活 Skill 发射步骤事件。
|
||||
*
|
||||
* @param block 工具使用块
|
||||
* @return 运行时事件
|
||||
* @return Skill 步骤事件,普通工具返回 null
|
||||
*/
|
||||
private AgentRuntimeEvent toolCallEvent(ToolUseBlock block) {
|
||||
private AgentRuntimeEvent toolExecutionStartEvent(ToolUseBlock block) {
|
||||
AgentSkillBinding activeBinding = activeSkillBinding();
|
||||
AgentRuntimeEvent event = baseEvent(activeBinding == null
|
||||
? AgentRuntimeEventType.TOOL_CALL
|
||||
: AgentRuntimeEventType.SKILL_STEP);
|
||||
if (!emitSkillStep || activeBinding == null) {
|
||||
return null;
|
||||
}
|
||||
AgentRuntimeEvent event = baseEvent(AgentRuntimeEventType.SKILL_STEP);
|
||||
if (block != null) {
|
||||
event.setToolCallId(block.getId());
|
||||
event.getPayload().put("name", block.getName());
|
||||
@@ -359,6 +494,7 @@ public class AgentScopeToolAdapter {
|
||||
* @return 工具审批事件
|
||||
*/
|
||||
private AgentRuntimeEvent toolApprovalRequiredEvent(ToolUseBlock toolUseBlock, AgentPendingState pendingState) {
|
||||
AgentRuntimeExecutionContext currentRequest = currentRequest();
|
||||
AgentRuntimeEvent event = baseEvent(AgentRuntimeEventType.TOOL_APPROVAL_REQUIRED);
|
||||
if (pendingState != null && pendingState.getToolCallId() != null) {
|
||||
event.setToolCallId(pendingState.getToolCallId());
|
||||
@@ -367,8 +503,8 @@ public class AgentScopeToolAdapter {
|
||||
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("sessionId", currentRequest.getSessionId());
|
||||
payload.put("agentId", currentRequest.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());
|
||||
@@ -404,11 +540,8 @@ public class AgentScopeToolAdapter {
|
||||
* @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());
|
||||
AgentRuntimeExecutionContext currentRequest = currentRequest();
|
||||
AgentRuntimeEvent event = currentEventBridge().event(type);
|
||||
event.getMetadata().put("toolCategory", toolSpec.getCategory().name());
|
||||
event.getMetadata().put("visibility", toolSpec.getVisibility().name());
|
||||
appendSkillPayload(event.getMetadata(), activeSkillBinding());
|
||||
@@ -455,5 +588,16 @@ public class AgentScopeToolAdapter {
|
||||
Object success = event.getMetadata().get("success");
|
||||
return !(success instanceof Boolean) || Boolean.TRUE.equals(success);
|
||||
}
|
||||
|
||||
private AgentRuntimeExecutionContext currentRequest() {
|
||||
return turnContextHolder == null ? request : turnContextHolder.executionContext(request);
|
||||
}
|
||||
|
||||
private AgentRuntimeEventBridge currentEventBridge() {
|
||||
if (turnContextHolder != null && turnContextHolder.eventBridge().isPresent()) {
|
||||
return turnContextHolder.eventBridge().get();
|
||||
}
|
||||
return new AgentRuntimeEventBridge(request, turnContextHolder);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user