feat: 先进智能体运行时基座 v1
- 支持高层调用进行可配置智能体业务开发 - 封装基于 agentscope-java - 支持 tool、skill、知识库等封装 - 支持 tool 审批 - 支持智能体事件回传 - 等等
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user