mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
Python: DevUI Fix Serialization, Timestamp and Other Issues (#1584)
* refactor(devui): adopt standard OpenAI lifecycle events for agents and workflows - Replace custom workflow events with OpenAI Responses API standard lifecycle events - Add AgentStartedEvent, AgentCompletedEvent, AgentFailedEvent for clean separation - Implement ExecutorActionItem for workflow executor tracking - Convert informational events to trace events to reduce noise - Update README mapper table with comprehensive event mappings - Maintain full backward compatibility with legacy events * fix(devui): resolve timestamp overwriting and Content serialization errors - Fix tool call timestamps being overwritten on each render (#1483) - Add recursive Content serialization to handle ChatMessage and nested objects (#1548) - Implement proper MCP tool cleanup on server shutdown - Add timestamp field to function_result.complete events - Enhance credential and client resource cleanup Fixes #1483, #1548 Partial improvements for #1476
This commit is contained in:
committed by
GitHub
Unverified
parent
064ee8afbe
commit
6b66a34609
+101
-30
@@ -408,46 +408,82 @@ export function WorkflowView({
|
||||
// This preserves the workflow's final output for display
|
||||
};
|
||||
|
||||
// Extract workflow events from OpenAI events for executor tracking
|
||||
// Extract workflow and output item events from OpenAI events for executor tracking
|
||||
const workflowEvents = useMemo(() => {
|
||||
return openAIEvents.filter(
|
||||
(event) => event.type === "response.workflow_event.complete"
|
||||
(event) =>
|
||||
event.type === "response.output_item.added" ||
|
||||
event.type === "response.output_item.done" ||
|
||||
event.type === "response.created" ||
|
||||
event.type === "response.in_progress" ||
|
||||
event.type === "response.completed" ||
|
||||
event.type === "response.failed" ||
|
||||
// Keep legacy support for older backends
|
||||
event.type === "response.workflow_event.complete"
|
||||
);
|
||||
}, [openAIEvents]);
|
||||
|
||||
// Extract executor history from workflow events (filter out workflow-level events)
|
||||
const executorHistory = useMemo(() => {
|
||||
return workflowEvents
|
||||
.filter((event) => {
|
||||
if ("data" in event && event.data && typeof event.data === "object") {
|
||||
const data = event.data as Record<string, unknown>;
|
||||
// Filter out workflow-level events (those without executor_id)
|
||||
// These include: WorkflowStartedEvent, WorkflowOutputEvent, WorkflowStatusEvent, etc.
|
||||
return data.executor_id != null;
|
||||
const history: Array<{
|
||||
executorId: string;
|
||||
message: string;
|
||||
timestamp: string;
|
||||
status: "running" | "completed" | "error";
|
||||
}> = [];
|
||||
|
||||
workflowEvents.forEach((event) => {
|
||||
// Handle new standard OpenAI events
|
||||
if (
|
||||
event.type === "response.output_item.added" ||
|
||||
event.type === "response.output_item.done"
|
||||
) {
|
||||
const item = (event as any).item;
|
||||
if (item && item.type === "executor_action" && item.executor_id) {
|
||||
history.push({
|
||||
executorId: item.executor_id,
|
||||
message:
|
||||
event.type === "response.output_item.added"
|
||||
? "Executor started"
|
||||
: item.status === "completed"
|
||||
? "Executor completed"
|
||||
: item.status === "failed"
|
||||
? "Executor failed"
|
||||
: "Executor processing",
|
||||
timestamp: new Date().toISOString(),
|
||||
status:
|
||||
item.status === "completed"
|
||||
? "completed"
|
||||
: item.status === "failed"
|
||||
? "error"
|
||||
: "running",
|
||||
});
|
||||
}
|
||||
return false;
|
||||
})
|
||||
.map((event) => {
|
||||
if ("data" in event && event.data && typeof event.data === "object") {
|
||||
const data = event.data as Record<string, unknown>;
|
||||
return {
|
||||
}
|
||||
// Legacy support for older backends
|
||||
else if (
|
||||
event.type === "response.workflow_event.complete" &&
|
||||
"data" in event &&
|
||||
event.data &&
|
||||
typeof event.data === "object"
|
||||
) {
|
||||
const data = event.data as Record<string, unknown>;
|
||||
if (data.executor_id != null) {
|
||||
history.push({
|
||||
executorId: String(data.executor_id),
|
||||
message: String(data.event_type || "Processing"),
|
||||
timestamp: String(data.timestamp || new Date().toISOString()),
|
||||
status: String(data.event_type || "").includes("Completed")
|
||||
? ("completed" as const)
|
||||
? "completed"
|
||||
: String(data.event_type || "").includes("Error")
|
||||
? ("error" as const)
|
||||
: ("running" as const),
|
||||
};
|
||||
? "error"
|
||||
: "running",
|
||||
});
|
||||
}
|
||||
return {
|
||||
executorId: "unknown",
|
||||
message: "Processing",
|
||||
timestamp: new Date().toISOString(),
|
||||
status: "running" as const,
|
||||
};
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return history;
|
||||
}, [workflowEvents]);
|
||||
|
||||
// Track active executors
|
||||
@@ -525,16 +561,51 @@ export function WorkflowView({
|
||||
);
|
||||
|
||||
for await (const openAIEvent of streamGenerator) {
|
||||
// Only store workflow events in state for performance
|
||||
// Text deltas are processed directly without state updates
|
||||
if (openAIEvent.type === "response.workflow_event.complete") {
|
||||
// Store workflow-related events for tracking
|
||||
if (
|
||||
openAIEvent.type === "response.output_item.added" ||
|
||||
openAIEvent.type === "response.output_item.done" ||
|
||||
openAIEvent.type === "response.created" ||
|
||||
openAIEvent.type === "response.in_progress" ||
|
||||
openAIEvent.type === "response.completed" ||
|
||||
openAIEvent.type === "response.failed" ||
|
||||
openAIEvent.type === "response.workflow_event.complete" // Legacy
|
||||
) {
|
||||
setOpenAIEvents((prev) => [...prev, openAIEvent]);
|
||||
}
|
||||
|
||||
// Pass to debug panel
|
||||
onDebugEvent(openAIEvent);
|
||||
|
||||
// Handle workflow events to track current executor
|
||||
// Handle new standard OpenAI events
|
||||
if (openAIEvent.type === "response.output_item.added") {
|
||||
const item = (openAIEvent as any).item;
|
||||
if (item && item.type === "executor_action" && item.executor_id) {
|
||||
currentStreamingExecutor.current = item.executor_id;
|
||||
// Initialize output for this executor if not exists
|
||||
if (!executorOutputs.current[item.executor_id]) {
|
||||
executorOutputs.current[item.executor_id] = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle workflow completion
|
||||
if (openAIEvent.type === "response.completed") {
|
||||
// Workflow completed successfully
|
||||
// Final output is already in workflowResult from text streaming
|
||||
}
|
||||
|
||||
// Handle workflow failure
|
||||
if (openAIEvent.type === "response.failed") {
|
||||
const error = (openAIEvent as any).response?.error;
|
||||
if (error) {
|
||||
setWorkflowError(
|
||||
typeof error === "string" ? error : JSON.stringify(error)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Legacy support for older backends
|
||||
if (
|
||||
openAIEvent.type === "response.workflow_event.complete" &&
|
||||
"data" in openAIEvent &&
|
||||
|
||||
Reference in New Issue
Block a user