fix: 保留模型异常后的会话上下文
- 保存模型失败与无正文取消路径中的 AgentScope 会话 - 补充失败和推理中断场景的上下文恢复测试
This commit is contained in:
@@ -689,6 +689,7 @@ public class AgentScopeReActRuntime implements AgentRuntime {
|
|||||||
saveSession();
|
saveSession();
|
||||||
return Flux.just(cancelled(context));
|
return Flux.just(cancelled(context));
|
||||||
}
|
}
|
||||||
|
saveSession();
|
||||||
return Flux.just(failed(context, error));
|
return Flux.just(failed(context, error));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -733,7 +734,8 @@ public class AgentScopeReActRuntime implements AgentRuntime {
|
|||||||
* 将取消前已输出的助手内容补写入 AgentScope memory 并保存 session。
|
* 将取消前已输出的助手内容补写入 AgentScope memory 并保存 session。
|
||||||
*
|
*
|
||||||
* <p>AgentScope 的正常完成路径会自行把最终助手消息写入 memory。取消订阅时不会触发
|
* <p>AgentScope 的正常完成路径会自行把最终助手消息写入 memory。取消订阅时不会触发
|
||||||
* 完成路径,因此这里仅在已有非空助手内容时补写一次,确保下一轮对话能拿到中断前上下文。</p>
|
* 完成路径,因此这里仅在已有非空助手内容时补写一次,并始终保存已经进入 memory 的
|
||||||
|
* 用户消息,确保下一轮对话能拿到中断前上下文。</p>
|
||||||
*
|
*
|
||||||
* @param finalText 当前已累计的助手文本
|
* @param finalText 当前已累计的助手文本
|
||||||
* @param finalMessage 当前已捕获的结构化助手消息
|
* @param finalMessage 当前已捕获的结构化助手消息
|
||||||
@@ -741,10 +743,9 @@ public class AgentScopeReActRuntime implements AgentRuntime {
|
|||||||
private void persistPartialAssistantOnCancel(StringBuilder finalText,
|
private void persistPartialAssistantOnCancel(StringBuilder finalText,
|
||||||
AtomicReference<AgentMessage> finalMessage) {
|
AtomicReference<AgentMessage> finalMessage) {
|
||||||
AgentMessage partialMessage = partialAssistantMessage(finalText, finalMessage);
|
AgentMessage partialMessage = partialAssistantMessage(finalText, finalMessage);
|
||||||
if (partialMessage == null) {
|
if (partialMessage != null) {
|
||||||
return;
|
agent.getMemory().addMessage(messageAdapter.toMsg(partialMessage));
|
||||||
}
|
}
|
||||||
agent.getMemory().addMessage(messageAdapter.toMsg(partialMessage));
|
|
||||||
saveSession();
|
saveSession();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -838,6 +838,59 @@ public class AgentScopeStatefulRuntimeTest {
|
|||||||
&& "partial answer".equals(message.getTextContent())));
|
&& "partial answer".equals(message.getTextContent())));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldPersistUserMessageWhenModelFails() {
|
||||||
|
InMemoryAgentSessionStore sessionStore = new InMemoryAgentSessionStore();
|
||||||
|
AgentInitRequest request = initRequest();
|
||||||
|
request.setSessionStore(sessionStore);
|
||||||
|
AgentScopeReActRuntime runtime = runtimeWithError(new IllegalStateException("model unavailable"));
|
||||||
|
runtime.init(request);
|
||||||
|
|
||||||
|
List<AgentRuntimeEvent> events = runtime.stream(
|
||||||
|
AgentMessage.text(AgentMessageRole.USER, "remember this request"))
|
||||||
|
.collectList()
|
||||||
|
.block();
|
||||||
|
|
||||||
|
Assert.assertTrue(events.stream().anyMatch(event -> event.getEventType() == AgentRuntimeEventType.FAILED));
|
||||||
|
Assert.assertTrue(sessionStore.exists("session-1"));
|
||||||
|
AgentScopeReActRuntime restoredRuntime = fakeRuntime();
|
||||||
|
restoredRuntime.init(request);
|
||||||
|
Assert.assertTrue(restoredRuntime.getAgent().getMemory().getMessages().stream()
|
||||||
|
.anyMatch(message -> message.getRole() == MsgRole.USER
|
||||||
|
&& "remember this request".equals(message.getTextContent())));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldPersistUserMessageWhenReasoningOnlyStreamIsCancelled() throws Exception {
|
||||||
|
InMemoryAgentSessionStore sessionStore = new InMemoryAgentSessionStore();
|
||||||
|
AgentInitRequest request = initRequest();
|
||||||
|
request.setSessionStore(sessionStore);
|
||||||
|
AgentScopeReActRuntime runtime = runtimeWithModel(List.of(
|
||||||
|
ChatResponse.builder()
|
||||||
|
.id("reasoning-only")
|
||||||
|
.content(List.of(ThinkingBlock.builder().thinking("still thinking").build()))
|
||||||
|
.build()), Duration.ofSeconds(5));
|
||||||
|
runtime.init(request);
|
||||||
|
|
||||||
|
CompletableFuture<AgentRuntimeEvent> firstReasoning = new CompletableFuture<>();
|
||||||
|
reactor.core.Disposable disposable = runtime.stream(
|
||||||
|
AgentMessage.text(AgentMessageRole.USER, "remember reasoning request"))
|
||||||
|
.subscribe(event -> {
|
||||||
|
if (event.getEventType() == AgentRuntimeEventType.REASONING_DELTA) {
|
||||||
|
firstReasoning.complete(event);
|
||||||
|
}
|
||||||
|
}, firstReasoning::completeExceptionally);
|
||||||
|
firstReasoning.get(3, TimeUnit.SECONDS);
|
||||||
|
|
||||||
|
disposable.dispose();
|
||||||
|
awaitCondition(() -> sessionStore.exists("session-1"));
|
||||||
|
AgentScopeReActRuntime restoredRuntime = fakeRuntime();
|
||||||
|
restoredRuntime.init(request);
|
||||||
|
Assert.assertTrue(restoredRuntime.getAgent().getMemory().getMessages().stream()
|
||||||
|
.anyMatch(message -> message.getRole() == MsgRole.USER
|
||||||
|
&& "remember reasoning request".equals(message.getTextContent())));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void shouldNotDuplicateNormalToolEventsFromMainStream() {
|
public void shouldNotDuplicateNormalToolEventsFromMainStream() {
|
||||||
AgentInitRequest request = initRequest();
|
AgentInitRequest request = initRequest();
|
||||||
@@ -1636,6 +1689,37 @@ public class AgentScopeStatefulRuntimeTest {
|
|||||||
new AgentScopeMessageAdapter());
|
new AgentScopeMessageAdapter());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建模型调用直接失败的运行时。
|
||||||
|
*
|
||||||
|
* @param error 模型异常
|
||||||
|
* @return 测试运行时
|
||||||
|
*/
|
||||||
|
private AgentScopeReActRuntime runtimeWithError(Throwable error) {
|
||||||
|
AgentScopeModelFactory modelFactory = new AgentScopeModelFactory() {
|
||||||
|
@Override
|
||||||
|
public Model create(AgentModelSpec modelSpec,
|
||||||
|
com.easyagents.agent.runtime.model.AgentGenerationOptions generationOptions) {
|
||||||
|
return new Model() {
|
||||||
|
@Override
|
||||||
|
public Flux<ChatResponse> stream(List<Msg> messages,
|
||||||
|
List<ToolSchema> toolSchemas,
|
||||||
|
GenerateOptions options) {
|
||||||
|
return Flux.error(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getModelName() {
|
||||||
|
return modelSpec == null ? "fake-model" : modelSpec.getModelName();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return new AgentScopeReActRuntime(modelFactory, new AgentScopeToolAdapter(),
|
||||||
|
new AgentScopeKnowledgeAdapter(), new AgentScopeMemoryAdapter(), new AgentScopeSkillAdapter(),
|
||||||
|
new AgentScopeMessageAdapter());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建每次模型调用仅返回下一条预设响应的运行时。
|
* 创建每次模型调用仅返回下一条预设响应的运行时。
|
||||||
*
|
*
|
||||||
|
|||||||
Reference in New Issue
Block a user