Dataweaver dev#381
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces a disambiguation flow for broad or exploratory queries, updates the streaming query logic to handle skipped places and terminal states more robustly, and adds a 30-second timeout to MCP calls. The review feedback highlights a potential race condition in QueryProvider when handling disambiguation results that could lead to state inconsistency, suggests adding defensive fallbacks for LLM-generated fields in the home page UI, and recommends logging caught errors when tool calls fail or time out to improve production observability.
| if (result.disambiguation) { | ||
| // Handle disambiguation if present | ||
| queryFail(active.nodeId); | ||
| querySetProcessing(false); | ||
| querySetStatus(STATUS.idle); | ||
| break; | ||
| } |
There was a problem hiding this comment.
There is a race condition and state inconsistency here when handling result.disambiguation.
When a disambiguation is found, queryFail is called, which sets the node status to 'error' and unregisters cards. However, the SSE stream is still active and will eventually emit a STREAM_EVENT.complete event. When STREAM_EVENT.complete is processed, it calls queryComplete which overwrites the node status back to 'complete' and sets cardIds to the stale active.cardIds (which contains card IDs that were already unregistered by queryFail). This leads to state inconsistency and potential UI bugs.
To fix this, we should:
- Clean up any remaining loading cards.
- Call
queryComplete(active.nodeId, [])to cleanly complete the query with no cards. - Set
activeQueryRef.current = nullto ignore any subsequent stream events (preventing the race condition). - Set
querySetStatus(STATUS.complete)so the home page'suseEffect(which expectsSTATUS.complete) is triggered correctly to show the follow-up/disambiguation UI.
if (result.disambiguation) {
const { cardUnregister: unregisterOrphan, cards } = store.getState();
for (const id of active.cardIds) {
const card = cards[id];
if (card?.type === 'loading') {
unregisterOrphan(id);
}
}
queryComplete(active.nodeId, []);
querySetProcessing(false);
querySetStatus(STATUS.complete);
activeQueryRef.current = null;
break;
}
| setFollowUp({ | ||
| question: node.query, | ||
| answer: disambiguation.summary, | ||
| followUp: disambiguation.question, | ||
| prompts: disambiguation.options, | ||
| }); |
There was a problem hiding this comment.
It is safer to add defensive fallbacks when parsing LLM-generated JSON responses. Even though the schema defines these fields as required, LLMs can occasionally return malformed or incomplete objects. Adding default values prevents potential runtime crashes when rendering the follow-up UI.
| setFollowUp({ | |
| question: node.query, | |
| answer: disambiguation.summary, | |
| followUp: disambiguation.question, | |
| prompts: disambiguation.options, | |
| }); | |
| setFollowUp({ | |
| question: node.query, | |
| answer: disambiguation.summary || '', | |
| followUp: disambiguation.question || '', | |
| prompts: Array.isArray(disambiguation.options) ? disambiguation.options : [], | |
| }); |
| } catch (err: unknown) { | ||
| // If the caller aborted, re-throw so the outer loop exits cleanly. | ||
| if (signal?.aborted) throw err; | ||
| // Tool call timed out or failed — return empty so the place is skipped. | ||
| return { text: '', resolvedPlaceDcid }; | ||
| } |
There was a problem hiding this comment.
When a tool call fails or times out, the error is caught and the place is silently skipped. This makes debugging in production extremely difficult because there is no server-side log of the failure. We should log the error to console.error before returning.
} catch (err: unknown) {
if (signal?.aborted) throw err;
console.error('[Data Discovery] Tool call "' + fcName + '" failed:', err);
return { text: '', resolvedPlaceDcid };
}
No description provided.