Skip to content

Commit 16cc808

Browse files
committed
chore: release v3.2.3
1 parent 2d734f7 commit 16cc808

4 files changed

Lines changed: 147 additions & 63 deletions

File tree

RELEASE_NOTES.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ This file summarizes the release notes inferred from git tags (tag message/annot
44

55
---
66

7+
## v3.2.3
8+
- Release v3.2.3: Recover with partial answers or clarifying follow-ups after upstream stream failures when tool evidence already exists, prioritize gene-expression classification over connectivity for analytics, and bias broad transgene-expression requests toward short representative lists
9+
710
## v3.2.2
811
- Release v3.2.2: Restore production compatibility when explicit approved ELM values are not configured, add partial-summary and clarification fallbacks for broad tool-heavy queries, raise the tool-round budget to 10, and add named example asset generation
912

app/api/chat/route.js

Lines changed: 141 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -207,14 +207,14 @@ function classifyTopicCategory(message = '', toolUsage = {}) {
207207
return 'publications'
208208
}
209209

210-
if (toolUsage.vfb_run_query || /\b(connectivity|connectome|synapse|synaptic|nblast|projection|presynaptic|postsynaptic)\b/i.test(lowerMessage)) {
211-
return 'connectivity'
212-
}
213-
214210
if (/\b(gene|expression|transgene|gal4|driver|reporter)\b/i.test(lowerMessage)) {
215211
return 'gene expression'
216212
}
217213

214+
if (toolUsage.vfb_run_query || /\b(connectivity|connectome|synapse|synaptic|nblast|projection|presynaptic|postsynaptic)\b/i.test(lowerMessage)) {
215+
return 'connectivity'
216+
}
217+
218218
if (/\b(image|images|thumbnail|picture|pictures|visuali[sz]e|3d|scene)\b/i.test(lowerMessage)) {
219219
return 'images'
220220
}
@@ -320,7 +320,7 @@ async function getVfbMcpClient() {
320320

321321
const transport = new StreamableHTTPClientTransport(new URL(VFB_MCP_URL))
322322
const client = new Client(
323-
{ name: 'vfb-chat-client', version: '3.2.2' },
323+
{ name: 'vfb-chat-client', version: '3.2.3' },
324324
{ capabilities: {} }
325325
)
326326
await client.connect(transport)
@@ -333,7 +333,7 @@ async function getBiorxivMcpClient() {
333333

334334
const transport = new StreamableHTTPClientTransport(new URL(BIORXIV_MCP_URL))
335335
const client = new Client(
336-
{ name: 'vfb-chat-biorxiv', version: '3.2.2' },
336+
{ name: 'vfb-chat-biorxiv', version: '3.2.3' },
337337
{ capabilities: {} }
338338
)
339339
await client.connect(transport)
@@ -866,6 +866,7 @@ TOOL ECONOMY:
866866
- Prefer the fewest tool steps needed to produce a useful answer.
867867
- Do not keep calling tools just to exhaustively enumerate large result sets.
868868
- If the question is broad or combinatorial, stop once you have enough evidence to give a partial answer.
869+
- For broad gene-expression or transgene-pattern requests, prefer a short representative list (about 3-5 items) and ask how the user wants to narrow further instead of trying to enumerate everything in one turn.
869870
- If the question is broad or underspecified, it is good to ask 1-3 short clarifying questions instead of trying to enumerate everything immediately.
870871
- When stopping early, clearly summarize what you found so far and end with 2-4 direct clarifying questions the user can answer to narrow the query (for example: one dataset, one transmitter class, one neuron subtype, one brain region, or a capped number of results).
871872
@@ -1001,37 +1002,34 @@ async function readResponseStream(apiResponse, sendEvent) {
10011002
return { textAccumulator, functionCalls, responseId, failed, errorMessage }
10021003
}
10031004

1004-
async function requestToolLimitSummary({
1005+
async function requestNoToolFallbackResponse({
10051006
sendEvent,
10061007
conversationInput,
10071008
accumulatedItems,
1009+
partialAssistantText = '',
10081010
apiBaseUrl,
10091011
apiKey,
10101012
apiModel,
10111013
outboundAllowList,
10121014
toolUsage,
10131015
toolRounds,
1014-
maxToolRounds,
1015-
userMessage
1016+
statusMessage,
1017+
instruction
10161018
}) {
1017-
if (accumulatedItems.length === 0) return null
1019+
sendEvent('status', { message: statusMessage, phase: 'llm' })
10181020

1019-
sendEvent('status', { message: 'Summarizing partial results', phase: 'llm' })
1020-
1021-
const summaryInstruction = `The original user request was:
1022-
"${userMessage}"
1023-
1024-
The request hit the tool-round limit after ${toolRounds} rounds (current budget: ${maxToolRounds}).
1021+
const fallbackInput = [
1022+
...conversationInput,
1023+
...accumulatedItems
1024+
]
10251025

1026-
Using only the gathered tool outputs already provided in this conversation:
1027-
- give the best partial answer you can
1028-
- clearly say that the answer is partial because the request branched into too many tool steps
1029-
- summarize the strongest findings you already have
1030-
- end with 2-4 direct clarification questions the user can answer so you can continue in a narrower, lower-tool way
1026+
if (partialAssistantText.trim()) {
1027+
fallbackInput.push({ role: 'assistant', content: partialAssistantText.trim() })
1028+
}
10311029

1032-
Do not call tools. Do not ask to browse the web.`
1030+
fallbackInput.push({ role: 'user', content: instruction })
10331031

1034-
const summaryResponse = await fetch(`${apiBaseUrl}/responses`, {
1032+
const fallbackResponse = await fetch(`${apiBaseUrl}/responses`, {
10351033
method: 'POST',
10361034
headers: {
10371035
'Content-Type': 'application/json',
@@ -1040,21 +1038,17 @@ Do not call tools. Do not ask to browse the web.`
10401038
body: JSON.stringify({
10411039
model: apiModel,
10421040
instructions: systemPrompt,
1043-
input: [
1044-
...conversationInput,
1045-
...accumulatedItems,
1046-
{ role: 'user', content: summaryInstruction }
1047-
],
1041+
input: fallbackInput,
10481042
tools: [],
10491043
stream: true
10501044
})
10511045
})
10521046

1053-
if (!summaryResponse.ok) {
1047+
if (!fallbackResponse.ok) {
10541048
return null
10551049
}
10561050

1057-
const { textAccumulator, responseId, failed } = await readResponseStream(summaryResponse, sendEvent)
1051+
const { textAccumulator, responseId, failed } = await readResponseStream(fallbackResponse, sendEvent)
10581052
if (failed || !textAccumulator) {
10591053
return null
10601054
}
@@ -1068,10 +1062,54 @@ Do not call tools. Do not ask to browse the web.`
10681062
})
10691063
}
10701064

1065+
async function requestToolLimitSummary({
1066+
sendEvent,
1067+
conversationInput,
1068+
accumulatedItems,
1069+
apiBaseUrl,
1070+
apiKey,
1071+
apiModel,
1072+
outboundAllowList,
1073+
toolUsage,
1074+
toolRounds,
1075+
maxToolRounds,
1076+
userMessage
1077+
}) {
1078+
if (accumulatedItems.length === 0) return null
1079+
1080+
const summaryInstruction = `The original user request was:
1081+
"${userMessage}"
1082+
1083+
The request hit the tool-round limit after ${toolRounds} rounds (current budget: ${maxToolRounds}).
1084+
1085+
Using only the gathered tool outputs already provided in this conversation:
1086+
- give the best partial answer you can
1087+
- clearly say that the answer is partial because the request branched into too many tool steps
1088+
- summarize the strongest findings you already have
1089+
- end with 2-4 direct clarification questions the user can answer so you can continue in a narrower, lower-tool way
1090+
1091+
Do not call tools. Do not ask to browse the web.`
1092+
1093+
return requestNoToolFallbackResponse({
1094+
sendEvent,
1095+
conversationInput,
1096+
accumulatedItems,
1097+
apiBaseUrl,
1098+
apiKey,
1099+
apiModel,
1100+
outboundAllowList,
1101+
toolUsage,
1102+
toolRounds,
1103+
statusMessage: 'Summarizing partial results',
1104+
instruction: summaryInstruction
1105+
})
1106+
}
1107+
10711108
async function requestClarifyingFollowUp({
10721109
sendEvent,
10731110
conversationInput,
10741111
accumulatedItems,
1112+
partialAssistantText = '',
10751113
apiBaseUrl,
10761114
apiKey,
10771115
apiModel,
@@ -1081,8 +1119,6 @@ async function requestClarifyingFollowUp({
10811119
userMessage,
10821120
reason
10831121
}) {
1084-
sendEvent('status', { message: 'Clarifying next step', phase: 'llm' })
1085-
10861122
const clarificationInstruction = `The original user request was:
10871123
"${userMessage}"
10881124
@@ -1096,40 +1132,65 @@ Using only the existing conversation and any tool outputs already provided:
10961132
10971133
Do not call tools. Do not ask to browse the web.`
10981134

1099-
const clarificationResponse = await fetch(`${apiBaseUrl}/responses`, {
1100-
method: 'POST',
1101-
headers: {
1102-
'Content-Type': 'application/json',
1103-
...(apiKey ? { 'Authorization': `Bearer ${apiKey}` } : {})
1104-
},
1105-
body: JSON.stringify({
1106-
model: apiModel,
1107-
instructions: systemPrompt,
1108-
input: [
1109-
...conversationInput,
1110-
...accumulatedItems,
1111-
{ role: 'user', content: clarificationInstruction }
1112-
],
1113-
tools: [],
1114-
stream: true
1115-
})
1135+
return requestNoToolFallbackResponse({
1136+
sendEvent,
1137+
conversationInput,
1138+
accumulatedItems,
1139+
partialAssistantText,
1140+
apiBaseUrl,
1141+
apiKey,
1142+
apiModel,
1143+
outboundAllowList,
1144+
toolUsage,
1145+
toolRounds,
1146+
statusMessage: 'Clarifying next step',
1147+
instruction: clarificationInstruction
11161148
})
1149+
}
11171150

1118-
if (!clarificationResponse.ok) {
1119-
return null
1120-
}
1151+
async function requestStreamFailureRecovery({
1152+
sendEvent,
1153+
conversationInput,
1154+
accumulatedItems,
1155+
partialAssistantText = '',
1156+
apiBaseUrl,
1157+
apiKey,
1158+
apiModel,
1159+
outboundAllowList,
1160+
toolUsage,
1161+
toolRounds,
1162+
userMessage,
1163+
reason
1164+
}) {
1165+
if (accumulatedItems.length === 0 && !partialAssistantText.trim()) return null
11211166

1122-
const { textAccumulator, responseId, failed } = await readResponseStream(clarificationResponse, sendEvent)
1123-
if (failed || !textAccumulator) {
1124-
return null
1125-
}
1167+
const recoveryInstruction = `The original user request was:
1168+
"${userMessage}"
11261169
1127-
return buildSuccessfulTextResult({
1128-
responseText: textAccumulator,
1129-
responseId,
1170+
The previous attempt ended unexpectedly before a stable final answer was produced.
1171+
Reason: ${reason}.
1172+
1173+
Using only the existing conversation, any tool outputs already provided, and any partial answer text already shown above:
1174+
- give the best partial answer you can
1175+
- if the evidence is still too incomplete, say that briefly and ask 2-4 short clarifying questions
1176+
- prefer a short concrete answer over more questions if the available evidence already supports one
1177+
- do not invent missing facts
1178+
1179+
Do not call tools. Do not ask to browse the web.`
1180+
1181+
return requestNoToolFallbackResponse({
1182+
sendEvent,
1183+
conversationInput,
1184+
accumulatedItems,
1185+
partialAssistantText,
1186+
apiBaseUrl,
1187+
apiKey,
1188+
apiModel,
1189+
outboundAllowList,
11301190
toolUsage,
11311191
toolRounds,
1132-
outboundAllowList
1192+
statusMessage: 'Recovering partial answer',
1193+
instruction: recoveryInstruction
11331194
})
11341195
}
11351196

@@ -1155,6 +1216,25 @@ async function processResponseStream({
11551216
if (responseId) latestResponseId = responseId
11561217

11571218
if (failed) {
1219+
const recovery = await requestStreamFailureRecovery({
1220+
sendEvent,
1221+
conversationInput,
1222+
accumulatedItems,
1223+
partialAssistantText: textAccumulator,
1224+
apiBaseUrl,
1225+
apiKey,
1226+
apiModel,
1227+
outboundAllowList,
1228+
toolUsage,
1229+
toolRounds,
1230+
userMessage,
1231+
reason: errorMessage || 'The AI service returned an unexpected stream error.'
1232+
})
1233+
1234+
if (recovery) {
1235+
return recovery
1236+
}
1237+
11581238
return {
11591239
ok: false,
11601240
responseId: latestResponseId,
@@ -1247,6 +1327,7 @@ async function processResponseStream({
12471327
sendEvent,
12481328
conversationInput,
12491329
accumulatedItems,
1330+
partialAssistantText: textAccumulator,
12501331
apiBaseUrl,
12511332
apiKey,
12521333
apiModel,

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "vfb-chat-client",
3-
"version": "3.2.2",
3+
"version": "3.2.3",
44
"private": true,
55
"scripts": {
66
"dev": "next dev",

0 commit comments

Comments
 (0)