fix(assistant): store the transcript before message.end so the panel shows the answer #85
No files matched your search
@@ -1,5 +1,9 @@
|
||||
# Changelog
|
||||
|
||||
## Unreleased - Answer visible at the end of the stream
|
||||
|
||||
- The turn's redacted transcript (the question and answer parts, pending actions, stored events) is now written before `message.end` is sent. The panel reloads the conversation the moment the stream ends, and used to read the empty placeholders (`[]`) the transcript redactor stores until its flush, so the newest question and answer showed as empty bubbles until the panel was closed and reopened.
|
||||
|
||||
## Unreleased - Optional provider health
|
||||
|
||||
- An enabled provider that is not the default and not the only route for any configured routing (an enabled profile's simple/complex model, a budget downgrade model or a knowledge source's embedding model, with no fallback on another usable provider) is optional: when it is unreachable, its `assistant.provider.<id>` and `assistant.model.<id>` checks report DEGRADED with a message naming the provider requests use instead, so one unfinished local server no longer turns the whole `assistant` component DOWN. The default provider and sole routes still report DOWN. A failed provider probe still carries `reachable=false`, so the circuit breaker keeps refusing the provider.
|
||||
|
||||
@@ -1321,6 +1321,17 @@ public class TurnEngine implements Runnable
|
||||
String finalErrorText = errorText;
|
||||
safely(() -> emit(TurnEvent.ERROR, map("code", finalErrorCode, "message", finalErrorText)));
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
// the stream ends at message.end and the panel reloads the //
|
||||
// conversation at once, so the redacted transcript (message parts, //
|
||||
// pending actions, events) must be stored before that event is sent //
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
if(!alreadyTerminal)
|
||||
{
|
||||
safely(this::persistPendingEvents);
|
||||
safely(() -> transcript.flush(store));
|
||||
}
|
||||
safely(() -> emit(TurnEvent.MESSAGE_END, map("messageId", assistantMessage.getId(), "status", finalStatus, "stopReason", finalStopReason)));
|
||||
if(!alreadyTerminal)
|
||||
{
|
||||
|
||||
@@ -141,6 +141,27 @@ class TurnEngineTest extends BaseAssistantTest
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** The panel reloads the conversation as soon as message.end arrives, so the
|
||||
** question and the answer must already be stored then, not after it.
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testMessagesAreStoredBeforeMessageEnd() throws Exception
|
||||
{
|
||||
provider.thenText("Four posts are published.");
|
||||
|
||||
TurnRunner.Outcome outcome = runner.run(EDITOR, "how many published posts?");
|
||||
assertThat(outcome.end()).containsEntry("status", "COMPLETE");
|
||||
|
||||
List<AssistantMessage> stored = runner.messagesAtEnd();
|
||||
assertThat(stored).hasSize(2);
|
||||
assertThat(stored).extracting(AssistantMessage::getPartsJson).allSatisfy(parts -> assertThat(parts).isNotEqualTo("[]"));
|
||||
assertThat(stored.get(0).getPartsJson()).contains("how many published posts?");
|
||||
assertThat(stored.get(1).getPartsJson()).contains("Four posts are published.");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Parallel tool calls: all results in ONE user message, in order.
|
||||
*******************************************************************************/
|
||||
|
||||
@@ -27,6 +27,7 @@ import com.kingsrook.qbits.assistant.AssistantRuntime;
|
||||
import com.kingsrook.qbits.assistant.api.AssistantApi;
|
||||
import com.kingsrook.qbits.assistant.engine.RunningTurn;
|
||||
import com.kingsrook.qbits.assistant.engine.TurnEvent;
|
||||
import com.kingsrook.qbits.assistant.model.AssistantMessage;
|
||||
import com.kingsrook.qbits.assistant.model.AssistantToolCall;
|
||||
import com.kingsrook.qbits.assistant.persistence.AssistantStore;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
@@ -47,6 +48,8 @@ public class TurnRunner
|
||||
private final QInstance qInstance;
|
||||
private final AssistantQBitConfig config;
|
||||
|
||||
private List<AssistantMessage> messagesAtEnd = List.of();
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@@ -169,6 +172,10 @@ public class TurnRunner
|
||||
{
|
||||
List<TurnEvent> batch = running.getLog().after(last, 1000);
|
||||
events.addAll(batch);
|
||||
if(batch.stream().anyMatch(e -> TurnEvent.MESSAGE_END.equals(e.getType())))
|
||||
{
|
||||
messagesAtEnd = messagesOf(userId, convId);
|
||||
}
|
||||
if(!batch.isEmpty())
|
||||
{
|
||||
last = batch.get(batch.size() - 1).getSeq();
|
||||
@@ -194,6 +201,36 @@ public class TurnRunner
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** The conversation's stored messages, read the moment the run's message.end
|
||||
** was seen: what the panel finds when it reloads the conversation at the end
|
||||
** of the stream.
|
||||
*******************************************************************************/
|
||||
public List<AssistantMessage> messagesAtEnd()
|
||||
{
|
||||
return (messagesAtEnd);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** The stored messages of a conversation (read as the user).
|
||||
*******************************************************************************/
|
||||
private List<AssistantMessage> messagesOf(String userId, Integer conversationId) throws Exception
|
||||
{
|
||||
QContext.init(qInstance, AssistantTestFixture.ownerSession(userId, config));
|
||||
try
|
||||
{
|
||||
return (new AssistantStore().listMessages(userId, conversationId, null, 50));
|
||||
}
|
||||
finally
|
||||
{
|
||||
QContext.clear();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** The audited tool calls of a turn (read as the user).
|
||||
*******************************************************************************/
|
||||
|
||||
Reference in new issue
Block a user