Skip to content

Commit 5c74ada

Browse files
peterjEItanya
andauthored
display errors from the backend (#425)
Signed-off-by: Peter Jausovec <peter.jausovec@solo.io> Co-authored-by: Eitan Yarmush <eitan.yarmush@solo.io>
1 parent 4f47a62 commit 5c74ada

File tree

6 files changed

+113
-38
lines changed

6 files changed

+113
-38
lines changed

python/src/autogenstudio/sessionmanager/sessionmanager.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,10 +167,14 @@ async def start_stream(
167167
logger.error(f"Stream error for run {run_id}: {e}")
168168
traceback.print_exc()
169169

170+
# The messages[0].content isn't properly being serialized, so it
171+
# doesn't even get sent back to the client. (That's why we're seeing undefined in the UI)
172+
# I am using the stop_reason to send the error message back and specifically checking
173+
# for the message type (error).
170174
error_result = TeamResult(
171175
task_result=TaskResult(
172176
messages=[TextMessage(source="system", content=str(e))],
173-
stop_reason="An error occurred while processing this run",
177+
stop_reason=str(e),
174178
),
175179
usage="",
176180
duration=0,

ui/src/components/chat/ChatInterface.tsx

Lines changed: 11 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,18 @@ import { useState, useRef, useEffect } from "react";
55
import { ArrowBigUp, X, Loader2 } from "lucide-react";
66
import { Button } from "@/components/ui/button";
77
import { Textarea } from "@/components/ui/textarea";
8-
import type { Session, AgentMessageConfig, TextMessageConfig } from "@/types/datamodel";
8+
import type { Session, AgentMessageConfig } from "@/types/datamodel";
99
import { ScrollArea } from "@/components/ui/scroll-area";
1010
import ChatMessage from "@/components/chat/ChatMessage";
1111
import StreamingMessage from "./StreamingMessage";
12-
import TokenStatsDisplay, { calculateTokenStats } from "./TokenStats";
12+
import TokenStatsDisplay from "./TokenStats";
1313
import { TokenStats } from "@/lib/types";
1414
import StatusDisplay from "./StatusDisplay";
1515
import { createSession, getSessionMessages, checkSessionExists, updateSession } from "@/app/actions/sessions";
1616
import { getCurrentUserId } from "@/app/actions/utils";
17-
import { messageUtils } from "@/lib/utils";
1817
import { toast } from "sonner";
1918
import { useRouter } from "next/navigation";
19+
import { createMessageHandlers } from "@/lib/messageHandlers";
2020

2121
export type ChatStatus = "ready" | "thinking" | "error";
2222

@@ -49,6 +49,13 @@ export default function ChatInterface({ selectedAgentId, selectedSession, sessio
4949
const isCreatingSessionRef = useRef<boolean>(false);
5050
const [isFirstMessage, setIsFirstMessage] = useState<boolean>(!sessionId);
5151

52+
const { handleMessageEvent } = createMessageHandlers({
53+
setMessages,
54+
setIsStreaming,
55+
setStreamingContent,
56+
setTokenStats
57+
});
58+
5259
useEffect(() => {
5360
async function initializeChat() {
5461
// Skip completely if this is a first message session creation flow
@@ -251,37 +258,7 @@ export default function ChatInterface({ selectedAgentId, selectedSession, sessio
251258
if (eventData) {
252259
try {
253260
const eventDataJson = JSON.parse(eventData) as AgentMessageConfig;
254-
255-
if (messageUtils.isStreamingMessage(eventDataJson)) {
256-
// Set the streaming flag to true and concatenate the content
257-
setIsStreaming(true);
258-
setStreamingContent(prev => prev + eventDataJson.content);
259-
} else if (messageUtils.isTextMessageContent(eventDataJson)) {
260-
// The model usage is sent within the TextMessage, after the streaming is complete
261-
setTokenStats(prev => calculateTokenStats(prev, eventDataJson as TextMessageConfig));
262-
setIsStreaming(false);
263-
setStreamingContent("");
264-
if (eventDataJson.source !== "user") {
265-
// We don't want to add the user's message to the messages array (again), because
266-
// we already added it when the user sent the message.
267-
setMessages(prevMessages => [...prevMessages, eventDataJson]);
268-
}
269-
}
270-
else {
271-
// For tool call messages and other non-streaming messages,
272-
// add them to the messages array immediately but don't stop streaming
273-
if (messageUtils.isToolCallRequestEvent(eventDataJson) ||
274-
messageUtils.isToolCallExecutionEvent(eventDataJson) ||
275-
messageUtils.isToolCallSummaryMessage(eventDataJson)) {
276-
// Add tool call messages immediately without stopping streaming
277-
setMessages(prevMessages => [...prevMessages, eventDataJson]);
278-
} else {
279-
// For other non-tool, non-streaming messages, use original behavior
280-
setIsStreaming(false);
281-
setStreamingContent("");
282-
setMessages(prevMessages => [...prevMessages, eventDataJson]);
283-
}
284-
}
261+
handleMessageEvent(eventDataJson);
285262
} catch (error) {
286263
toast.error("Error parsing event data");
287264
console.error("Error parsing event data:", error, eventData);

ui/src/components/chat/ChatMessage.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,12 @@ export default function ChatMessage({ message, allMessages }: ChatMessageProps)
2525
return <ToolCallDisplay currentMessage={message} allMessages={allMessages} />;
2626
}
2727

28-
const { content, source } = message;
28+
let { content, source } = message;
29+
30+
const isErrorMessage = messageUtils.isErrorMessageContent(message);
31+
if (isErrorMessage) {
32+
content = message.data.task_result.stop_reason || "An error occurred";
33+
}
2934

3035
// Filter out system messages
3136
// TODO: Decide whether we want to filter out som agent
@@ -42,8 +47,9 @@ export default function ChatMessage({ message, allMessages }: ChatMessageProps)
4247
return <LLMCallModal content={String(message)} />;
4348
}
4449

50+
const messageBorderColor = isErrorMessage ? "border-l-red-500" : source === "user" ? "border-l-blue-500" : "border-l-violet-500";
4551

46-
return <div className={`flex items-center gap-2 text-sm border-l-2 py-2 px-4 ${source === "user" ? "border-l-blue-500" : "border-l-violet-500"}`}>
52+
return <div className={`flex items-center gap-2 text-sm border-l-2 py-2 px-4 ${messageBorderColor}`}>
4753
<div className="flex flex-col gap-1 w-full">
4854
{source !== "user" ? <div className="flex items-center gap-1">
4955
<KagentLogo className="w-4 h-4" />

ui/src/lib/messageHandlers.ts

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import { AgentMessageConfig, TextMessageConfig } from "@/types/datamodel";
2+
import { messageUtils } from "@/lib/utils";
3+
import { TokenStats } from "@/lib/types";
4+
import { calculateTokenStats } from "@/components/chat/TokenStats";
5+
6+
export type MessageHandlers = {
7+
setMessages: (updater: (prev: AgentMessageConfig[]) => AgentMessageConfig[]) => void;
8+
setIsStreaming: (value: boolean) => void;
9+
setStreamingContent: (updater: (prev: string) => string) => void;
10+
setTokenStats: (updater: (prev: TokenStats) => TokenStats) => void;
11+
};
12+
13+
export const createMessageHandlers = (handlers: MessageHandlers) => {
14+
const handleStreamingMessage = (message: AgentMessageConfig) => {
15+
handlers.setIsStreaming(true);
16+
handlers.setStreamingContent(prev => prev + message.content);
17+
};
18+
19+
const handleTextMessage = (message: AgentMessageConfig) => {
20+
handlers.setTokenStats(prev => calculateTokenStats(prev, message as TextMessageConfig));
21+
handlers.setIsStreaming(false);
22+
handlers.setStreamingContent(() => "");
23+
24+
// Only add non-user messages to the messages array
25+
if (message.source !== "user") {
26+
handlers.setMessages(prevMessages => [...prevMessages, message]);
27+
}
28+
};
29+
30+
const handleErrorMessage = (message: AgentMessageConfig) => {
31+
handlers.setMessages(prevMessages => [...prevMessages, message]);
32+
};
33+
34+
const handleToolCallMessage = (message: AgentMessageConfig) => {
35+
handlers.setMessages(prevMessages => [...prevMessages, message]);
36+
};
37+
38+
const handleOtherMessage = (message: AgentMessageConfig) => {
39+
handlers.setIsStreaming(false);
40+
handlers.setStreamingContent(() => "");
41+
handlers.setMessages(prevMessages => [...prevMessages, message]);
42+
};
43+
44+
const handleMessageEvent = (message: AgentMessageConfig) => {
45+
if (messageUtils.isStreamingMessage(message)) {
46+
handleStreamingMessage(message);
47+
return;
48+
}
49+
50+
if (messageUtils.isTextMessageContent(message)) {
51+
handleTextMessage(message);
52+
return;
53+
}
54+
55+
if (messageUtils.isErrorMessageContent(message)) {
56+
handleErrorMessage(message);
57+
return;
58+
}
59+
60+
// Handle tool call messages
61+
if (messageUtils.isToolCallRequestEvent(message) ||
62+
messageUtils.isToolCallExecutionEvent(message) ||
63+
messageUtils.isToolCallSummaryMessage(message)) {
64+
handleToolCallMessage(message);
65+
return;
66+
}
67+
68+
// Handle any other message types
69+
handleOtherMessage(message);
70+
};
71+
72+
return {
73+
handleMessageEvent
74+
};
75+
};

ui/src/lib/utils.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { LLMCall } from "@/components/chat/LLMCallModal";
22
import { ImageContent, TaskResultMessage } from "@/types/datamodel";
33
import { clsx, type ClassValue } from "clsx";
44
import { twMerge } from "tailwind-merge";
5-
import type { CompletionMessage, MemoryQueryEvent, ModelClientStreamingChunkEvent, TextMessageConfig, ToolCallExecutionEvent, ToolCallRequestEvent, ToolCallSummaryMessage } from "@/types/datamodel";
5+
import type { CompletionMessage, ErrorMessageConfig, MemoryQueryEvent, ModelClientStreamingChunkEvent, TextMessageConfig, ToolCallExecutionEvent, ToolCallRequestEvent, ToolCallSummaryMessage } from "@/types/datamodel";
66

77
export function cn(...inputs: ClassValue[]) {
88
return twMerge(clsx(inputs));
@@ -184,6 +184,10 @@ export const messageUtils = {
184184
return typeof content === "object" && content !== null && "content" in content && "type" in content && content.type === "TextMessage";
185185
},
186186

187+
isErrorMessageContent(content: unknown): content is ErrorMessageConfig {
188+
return typeof content === "object" && content !== null && "type" in content && content.type === "error";
189+
},
190+
187191
isUserTextMessageContent(content: unknown): content is TextMessageConfig {
188192
return messageUtils.isTextMessageContent(content) && content.source === "user";
189193
},

ui/src/types/datamodel.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,15 @@ export interface TextMessageConfig extends BaseMessageConfig {
7070
content: string;
7171
}
7272

73+
export interface ErrorMessageConfig extends BaseMessageConfig {
74+
data: {
75+
task_result: TaskResult;
76+
usage: string;
77+
duration: number;
78+
};
79+
type: "error";
80+
}
81+
7382
export interface MultiModalMessageConfig extends BaseMessageConfig {
7483
content: (string | ImageContent)[];
7584
}

0 commit comments

Comments
 (0)