Skip to content

Commit 830071e

Browse files
authored
fix(callout retry loop): don't retry automatically when dismising the error message, and validate tool_calls on abort or error. (#333) (#337)
Conflicts: src/components/ChatForm/ChatForm.tsx src/features/Chat/Thread/reducer.ts Changes to be committed: modified: src/components/ChatForm/ChatForm.tsx modified: src/features/Chat/Thread/actions.ts modified: src/features/Chat/Thread/reducer.ts modified: src/services/refact/types.ts
1 parent b37631c commit 830071e

File tree

4 files changed

+39
-21
lines changed

4 files changed

+39
-21
lines changed

src/components/ChatForm/ChatForm.tsx

Lines changed: 2 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ import {
1515
useIsOnline,
1616
useConfig,
1717
useAgentUsage,
18-
useSendChatRequest,
1918
useCapsForToolUse,
2019
USAGE_LIMIT_EXHAUSTED_MESSAGE,
2120
} from "../../hooks";
@@ -49,7 +48,7 @@ import {
4948
selectPreventSend,
5049
selectToolUse,
5150
} from "../../features/Chat";
52-
import { isUserMessage, telemetryApi } from "../../services/refact";
51+
import { telemetryApi } from "../../services/refact";
5352
import { push } from "../../features/Pages/pagesSlice";
5453

5554
export type ChatFormProps = {
@@ -68,7 +67,6 @@ export const ChatForm: React.FC<ChatFormProps> = ({
6867
const dispatch = useAppDispatch();
6968
const isStreaming = useAppSelector(selectIsStreaming);
7069
const isWaiting = useAppSelector(selectIsWaiting);
71-
const { retryFromIndex } = useSendChatRequest();
7270
const { isMultimodalitySupportedForCurrentModel } = useCapsForToolUse();
7371
const config = useConfig();
7472
const toolUse = useAppSelector(selectToolUse);
@@ -83,22 +81,7 @@ export const ChatForm: React.FC<ChatFormProps> = ({
8381
const messages = useAppSelector(selectMessages);
8482
const preventSend = useAppSelector(selectPreventSend);
8583

86-
const onClearError = useCallback(() => {
87-
dispatch(clearError());
88-
const userMessages = messages.filter(isUserMessage);
89-
90-
// getting second-to-last user message
91-
const lastSuccessfulUserMessage =
92-
userMessages.slice(-2, -1)[0] || userMessages[0];
93-
94-
const lastSuccessfulUserMessageIndex = messages.indexOf(
95-
lastSuccessfulUserMessage,
96-
);
97-
retryFromIndex(
98-
lastSuccessfulUserMessageIndex,
99-
lastSuccessfulUserMessage.content,
100-
);
101-
}, [dispatch, retryFromIndex, messages]);
84+
const onClearError = useCallback(() => dispatch(clearError()), [dispatch]);
10285

10386
const disableSend = useMemo(() => {
10487
// TODO: if interrupting chat some errors can occur

src/features/Chat/Thread/actions.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,10 @@ export const setMaxNewTokens = createAction<number>(
123123
"chatThread/setMaxNewTokens",
124124
);
125125

126+
export const fixBrokenToolMessages = createAction<PayloadWithId>(
127+
"chatThread/fixBrokenToolMessages",
128+
);
129+
126130
// TODO: This is the circular dep when imported from hooks :/
127131
const createAppAsyncThunk = createAsyncThunk.withTypes<{
128132
state: RootState;
@@ -309,7 +313,10 @@ export const chatAskQuestionThunk = createAppAsyncThunk<
309313
}
310314
const reader = response.body?.getReader();
311315
if (!reader) return;
312-
const onAbort = () => thunkAPI.dispatch(setPreventSend({ id: chatId }));
316+
const onAbort = () => {
317+
thunkAPI.dispatch(setPreventSend({ id: chatId }));
318+
thunkAPI.dispatch(fixBrokenToolMessages({ id: chatId }));
319+
};
313320
const onChunk = (json: Record<string, unknown>) => {
314321
const action = chatResponse({
315322
...(json as ChatResponse),
@@ -323,6 +330,7 @@ export const chatAskQuestionThunk = createAppAsyncThunk<
323330
// console.log("Catch called");
324331
thunkAPI.dispatch(doneStreaming({ id: chatId }));
325332
thunkAPI.dispatch(chatError({ id: chatId, message: err.message }));
333+
thunkAPI.dispatch(fixBrokenToolMessages({ id: chatId }));
326334
return thunkAPI.rejectWithValue(err.message);
327335
})
328336
.finally(() => {

src/features/Chat/Thread/reducer.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,14 @@ import {
3131
setMaxNewTokens,
3232
setAutomaticPatch,
3333
setLastUserMessageId,
34+
fixBrokenToolMessages,
3435
} from "./actions";
3536
import { formatChatResponse } from "./utils";
36-
import { DEFAULT_MAX_NEW_TOKENS } from "../../../services/refact";
37+
import {
38+
DEFAULT_MAX_NEW_TOKENS,
39+
isToolCallMessage,
40+
validateToolCall,
41+
} from "../../../services/refact";
3742

3843
const createChatThread = (
3944
tool_use: ToolUse,
@@ -263,4 +268,16 @@ export const chatReducer = createReducer(initialState, (builder) => {
263268
builder.addCase(setMaxNewTokens, (state, action) => {
264269
state.max_new_tokens = action.payload;
265270
});
271+
272+
builder.addCase(fixBrokenToolMessages, (state, action) => {
273+
if (action.payload.id !== state.thread.id) return state;
274+
if (state.thread.messages.length === 0) return state;
275+
const lastMessage = state.thread.messages[state.thread.messages.length - 1];
276+
if (!isToolCallMessage(lastMessage)) return state;
277+
if (lastMessage.tool_calls.every(validateToolCall)) return state;
278+
const validToolCalls = lastMessage.tool_calls.filter(validateToolCall);
279+
const messages = state.thread.messages.slice(0, -1);
280+
const newMessage = { ...lastMessage, tool_calls: validToolCalls };
281+
state.thread.messages = [...messages, newMessage];
282+
});
266283
});

src/services/refact/types.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,16 @@ function isToolCall(call: unknown): call is ToolCall {
4646
return true;
4747
}
4848

49+
export const validateToolCall = (toolCall: ToolCall) => {
50+
if (!isToolCall(toolCall)) return false;
51+
try {
52+
JSON.parse(toolCall.function.arguments);
53+
return true;
54+
} catch {
55+
return false;
56+
}
57+
};
58+
4959
type ToolContent = string | MultiModalToolContent[];
5060

5161
export function isToolContent(json: unknown): json is ToolContent {

0 commit comments

Comments
 (0)