Skip to content

Comments

fix: resolve streaming not displaying for ReAct/simple agents#152

Merged
Mile-Away merged 1 commit intomainfrom
fix/frontend-bubble
Jan 11, 2026
Merged

fix: resolve streaming not displaying for ReAct/simple agents#152
Mile-Away merged 1 commit intomainfrom
fix/frontend-bubble

Conversation

@xinquiry
Copy link
Collaborator

@xinquiry xinquiry commented Jan 11, 2026

  • ChatBubble was rendering empty message.content instead of phase.streamedContent for react agents
  • Add fallback phase creation in streaming_start for agents without node_start events
  • Emit synthetic node_start in backend for prebuilt agents missing langgraph_node metadata

变更内容

  • 新功能
  • 修复 Bug
  • 增强重构
  • 其他(请描述)

简要描述本次 PR 的主要变更内容。

相关 Issue

请关联相关 Issue(如有):#编号

检查清单

默认已勾选,如不满足,请检查。

  • 已在本地测试通过
  • 已补充/更新相关文档
  • 已添加测试用例
  • 代码风格已经过 pre-commit 钩子检查

其他说明

如有特殊说明或注意事项,请补充。

Summary by Sourcery

修复 ReAct/简单代理的流式显示问题:通过阶段路由流式输出,并在缺少代理阶段/事件时合成缺失的代理阶段/事件。

Bug 修复:

  • 确保 ReAct/简单代理的聊天消息能够从执行阶段渲染流式内容,并在需要时安全回退到原始消息内容。
  • 当某个代理执行在接收到 streaming_start 事件时尚未包含任何阶段时,自动创建一个默认的“运行中”响应阶段。
  • 对于不包含 langgraph_node 元数据的预构建代理,发出合成的 node_start 事件,以便将流式事件正确接入代理时间线。
Original summary in English

Summary by Sourcery

Fix streaming display for ReAct/simple agents by routing streaming output through phases and synthesizing missing agent phases/events when they are not present.

Bug Fixes:

  • Ensure chat messages for ReAct/simple agents render streamed content from execution phases with a safe fallback to the original message content.
  • Automatically create a default running response phase when a streaming_start event arrives for an agent execution that has no phases.
  • Emit synthetic node_start events for prebuilt agents that do not include langgraph_node metadata so that streaming events are wired into the agent timeline.

- ChatBubble was rendering empty message.content instead of
  phase.streamedContent for react agents
- Add fallback phase creation in streaming_start for agents without
  node_start events
- Emit synthetic node_start in backend for prebuilt agents missing
  langgraph_node metadata

Co-Authored-By: Claude <noreply@anthropic.com>
@sourcery-ai
Copy link
Contributor

sourcery-ai bot commented Jan 11, 2026

Reviewer's Guide

调整在 UI 中展示 ReAct/simple 代理的流式内容的方式,并确保后端/Redux 发出并创建兼容的 phases 和 node_start 事件,从而即使是没有 langgraph 元数据的预构建代理,流式内容也能正确显示。

没有 langgraph_node 元数据的 ReAct 代理流式传输流程时序图

sequenceDiagram
    actor User
    participant Frontend as Frontend_ChatUI
    participant Store as Redux_ChatSlice
    participant Backend as Backend_LangchainHandler
    participant EventStream as Event_Stream

    User->>Frontend: Send message to ReAct agent
    Frontend->>Backend: HTTP request / websocket start

    Backend->>Backend: _handle_messages_mode(data, ctx)
    Backend->>Backend: Detect ctx.is_streaming == False
    alt No current_node and ctx.event_ctx exists
        Backend->>Backend: AgentEventStreamHandler.create_node_start_event(ctx, agent)
        Backend-->>EventStream: node_start event (synthetic)
    end
    Backend->>Backend: Set ctx.is_streaming = True
    Backend-->>EventStream: streaming_start event

    EventStream-->>Store: streaming_start
    Store->>Store: Find running agentExecution message
    alt No phases on execution
        Store->>Store: Create default Phase id=response
        Store->>Store: Set execution.currentNode = response
    end

    loop For each streaming_chunk
        Backend-->>EventStream: streaming_chunk event
        EventStream-->>Store: streaming_chunk
        Store->>Store: Append tokens to phase.streamedContent
    end

    Store-->>Frontend: Updated channel.messages
    Frontend->>Frontend: ChatBubble render
    alt agentExecution.agentType == react and phases exist
        Frontend->>Frontend: Select running or last phase
        Frontend->>Frontend: Render Markdown(phase.streamedContent)
    else agentExecution is null or no phases
        Frontend->>Frontend: Render Markdown(message.content)
    end

    User-->>Frontend: Sees live streaming response
Loading

AgentExecution phases 与 ChatBubble 流式变更的类图

classDiagram
    class Phase {
        +string id
        +string name
        +string status
        +number startedAt
        +Array~string~ nodes
        +string streamedContent
    }

    class AgentExecution {
        +string agentType
        +string status
        +string currentNode
        +Array~Phase~ phases
    }

    class Message {
        +string id
        +string content
        +AgentExecution agentExecution
    }

    class ChatBubble {
        +Message message
        +render(): JSXElement
        +selectReactPhaseContent(agentExecution: AgentExecution): string
    }

    class ChatSlice {
        +onStreamingStart(channelId: string, streamId: string): void
        +findRunningAgentMessage(messages: Array~Message~): number
        +ensureDefaultPhase(execution: AgentExecution): void
    }

    ChatBubble --> Message : renders
    Message --> AgentExecution : optional
    AgentExecution "1" o-- "*" Phase : phases
    ChatSlice --> AgentExecution : updates
    ChatSlice --> Phase : creates default Response phase

    class LangchainHandler {
        +_handle_messages_mode(data: any, ctx: StreamContext): AsyncGenerator
        +emitSyntheticNodeStart(ctx: StreamContext): void
    }

    class AgentEventStreamHandler {
        +create_node_start_event(ctx: StreamContext, nodeName: string): AgentEvent
    }

    class StreamingEventHandler {
        +create_streaming_start(streamId: string): StreamingEvent
    }

    LangchainHandler --> AgentEventStreamHandler : uses
    LangchainHandler --> StreamingEventHandler : uses
Loading

文件级变更

Change Details Files
为 ReAct/simple 代理消息渲染正确的内容,以便流式输出在聊天气泡中可见。
  • 用一个在 agentExecution 存在与 agentType 上分支的 IIFE 替换了简单的 markdownContent 表达式。
  • 对于普通聊天(没有 agentExecution),继续渲染 markdownContent。
  • 对于 react 代理,尝试从运行中的 phase 或最后一个具有 streamedContent 的 phase 中渲染 streamedContent,并包裹在 Markdown 组件中。
  • 当没有可用的 phase streamedContent 时,回退为渲染 markdownContent。
  • 对于多 phase 代理返回 null,这样它们的内容只会展示在时间线中。
web/src/components/layouts/components/ChatBubble.tsx
确保对于在未发出 node_start 事件的情况下开始流式传输的代理,存在一个默认的流式 phase。
  • 在处理 streaming_start 时,在当前频道中定位最后一个运行中的 agentExecution 消息。
  • 当一个 execution 没有 phases 时,推入一个合成的“Response”phase,状态为 running,带时间戳、空 nodes 和空 streamedContent。
  • 将 execution.currentNode 设置为该合成 phase 的 id,以便后续的流式分片可以将内容附加到该节点。
  • 保持现有行为:标记现有消息为 streaming,而不是创建新消息。
web/src/store/slices/chatSlice.ts
在后端为缺少 langgraph_node 元数据的预构建代理发出合成的 node_start 事件,使流式管线能够正确初始化。
  • 在 _handle_messages_mode 中发出 streaming_start 之前,在流式尚未开始时检查是否存在没有 current_node 的 event context。
  • 使用 AgentEventStreamHandler.create_node_start_event 创建一个合成的 node_start 事件,默认节点名称为 "agent"。
  • 如果创建了合成 node_start,则记录一条信息日志,并在 streaming_start 之前将该事件写入流中。
  • 在可能发出 node_start 之后,保留现有的 streaming_start 发出逻辑不变。
service/app/core/chat/langchain.py

Tips and commands

与 Sourcery 交互

  • 触发新评审: 在 pull request 中评论 @sourcery-ai review
  • 继续讨论: 直接回复 Sourcery 的评审评论。
  • 从评审评论生成 GitHub issue: 通过回复评审评论,请求 Sourcery 从该评论创建一个 issue。你也可以在评审评论中回复 @sourcery-ai issue 来从该评论创建 issue。
  • 生成 pull request 标题: 在 pull request 标题任意位置写上 @sourcery-ai 可随时生成标题。你也可以在 pull request 中评论 @sourcery-ai title 来(重新)生成标题。
  • 生成 pull request 摘要: 在 pull request 描述正文任意位置写上 @sourcery-ai summary,可在你想要的位置生成 PR 摘要。你也可以在 pull request 中评论 @sourcery-ai summary 来(重新)生成摘要。
  • 生成评审指南: 在 pull request 中评论 @sourcery-ai guide 来(重新)生成评审指南。
  • 一次性解决所有 Sourcery 评论: 在 pull request 中评论 @sourcery-ai resolve 来解决所有 Sourcery 评论。如果你已经处理完所有评论且不希望再看到它们,这会很有用。
  • 忽略所有 Sourcery 评审: 在 pull request 中评论 @sourcery-ai dismiss 来忽略所有现有的 Sourcery 评审。尤其适用于你想从一个全新的评审开始——别忘了评论 @sourcery-ai review 来触发新的评审!

自定义你的体验

访问你的控制面板 以:

  • 启用或禁用评审功能,比如 Sourcery 生成的 pull request 摘要、评审指南等。
  • 更改评审语言。
  • 添加、移除或编辑自定义评审说明。
  • 调整其他评审设置。

获取帮助

Original review guide in English

Reviewer's Guide

Adjusts how streaming content is surfaced for ReAct/simple agents in the UI and ensures backend/Redux emit and create compatible phases and node_start events so streaming displays correctly even for prebuilt agents without langgraph metadata.

Sequence diagram for streaming flow of ReAct agents without langgraph_node metadata

sequenceDiagram
    actor User
    participant Frontend as Frontend_ChatUI
    participant Store as Redux_ChatSlice
    participant Backend as Backend_LangchainHandler
    participant EventStream as Event_Stream

    User->>Frontend: Send message to ReAct agent
    Frontend->>Backend: HTTP request / websocket start

    Backend->>Backend: _handle_messages_mode(data, ctx)
    Backend->>Backend: Detect ctx.is_streaming == False
    alt No current_node and ctx.event_ctx exists
        Backend->>Backend: AgentEventStreamHandler.create_node_start_event(ctx, agent)
        Backend-->>EventStream: node_start event (synthetic)
    end
    Backend->>Backend: Set ctx.is_streaming = True
    Backend-->>EventStream: streaming_start event

    EventStream-->>Store: streaming_start
    Store->>Store: Find running agentExecution message
    alt No phases on execution
        Store->>Store: Create default Phase id=response
        Store->>Store: Set execution.currentNode = response
    end

    loop For each streaming_chunk
        Backend-->>EventStream: streaming_chunk event
        EventStream-->>Store: streaming_chunk
        Store->>Store: Append tokens to phase.streamedContent
    end

    Store-->>Frontend: Updated channel.messages
    Frontend->>Frontend: ChatBubble render
    alt agentExecution.agentType == react and phases exist
        Frontend->>Frontend: Select running or last phase
        Frontend->>Frontend: Render Markdown(phase.streamedContent)
    else agentExecution is null or no phases
        Frontend->>Frontend: Render Markdown(message.content)
    end

    User-->>Frontend: Sees live streaming response
Loading

Class diagram for AgentExecution phases and ChatBubble streaming changes

classDiagram
    class Phase {
        +string id
        +string name
        +string status
        +number startedAt
        +Array~string~ nodes
        +string streamedContent
    }

    class AgentExecution {
        +string agentType
        +string status
        +string currentNode
        +Array~Phase~ phases
    }

    class Message {
        +string id
        +string content
        +AgentExecution agentExecution
    }

    class ChatBubble {
        +Message message
        +render(): JSXElement
        +selectReactPhaseContent(agentExecution: AgentExecution): string
    }

    class ChatSlice {
        +onStreamingStart(channelId: string, streamId: string): void
        +findRunningAgentMessage(messages: Array~Message~): number
        +ensureDefaultPhase(execution: AgentExecution): void
    }

    ChatBubble --> Message : renders
    Message --> AgentExecution : optional
    AgentExecution "1" o-- "*" Phase : phases
    ChatSlice --> AgentExecution : updates
    ChatSlice --> Phase : creates default Response phase

    class LangchainHandler {
        +_handle_messages_mode(data: any, ctx: StreamContext): AsyncGenerator
        +emitSyntheticNodeStart(ctx: StreamContext): void
    }

    class AgentEventStreamHandler {
        +create_node_start_event(ctx: StreamContext, nodeName: string): AgentEvent
    }

    class StreamingEventHandler {
        +create_streaming_start(streamId: string): StreamingEvent
    }

    LangchainHandler --> AgentEventStreamHandler : uses
    LangchainHandler --> StreamingEventHandler : uses
Loading

File-Level Changes

Change Details Files
Render correct content for ReAct/simple agent messages so streaming output is visible in the chat bubble.
  • Replaced simple markdownContent expression with an IIFE that branches on agentExecution presence and agentType.
  • For regular chat (no agentExecution), continue rendering markdownContent.
  • For react agents, attempt to render streamedContent from the running phase or the last phase that has streamedContent, wrapped in the Markdown component.
  • Fallback to rendering markdownContent when no phase streamedContent is available.
  • Return null for multi-phase agents so their content is only displayed in the timeline.
web/src/components/layouts/components/ChatBubble.tsx
Ensure a default streaming phase exists for agents that start streaming without emitting node_start events.
  • On streaming_start handling, locate the last running agentExecution message in the current channel.
  • When an execution has no phases, push a synthetic "Response" phase with running status, timestamps, empty nodes, and empty streamedContent.
  • Set execution.currentNode to the synthetic phase id so subsequent streaming chunks can attach content to it.
  • Keep existing behavior of marking the existing message as streaming without creating a new message.
web/src/store/slices/chatSlice.ts
Emit synthetic node_start events in the backend for prebuilt agents that lack langgraph_node metadata so the streaming pipeline initializes correctly.
  • Before emitting streaming_start in _handle_messages_mode, check for an event context with no current_node when streaming has not yet begun.
  • Create a synthetic node_start event using AgentEventStreamHandler.create_node_start_event with a default node name of "agent".
  • If a synthetic node_start is created, log an informational message and yield the event into the stream before streaming_start.
  • Leave the existing streaming_start emission logic intact after potentially emitting node_start.
service/app/core/chat/langchain.py

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - 我发现了两个问题,并留下了一些总体反馈:

  • ChatBubble 中,react/simple agent 分支假定 agentExecution.phases 一定已定义;建议在访问 .length 或按索引访问之前先进行空值检查,或默认成空数组,以避免在 phases 缺失时产生运行时错误。
  • chatSlice.streaming_start 中,代码在重新赋值 message 之前直接修改了现有 state 对象上的 execution.phases;为保持 Redux 状态更新完全不可变,建议创建一个新的 agentExecution(以及新的 phases 数组)。
给 AI Agent 的提示词
请根据本次代码审查中的评论进行修改:

## 总体评论
-`ChatBubble` 中,react/simple agent 分支假定 `agentExecution.phases` 一定已定义;建议在访问 `.length` 或按索引访问之前先进行空值检查,或默认成空数组,以避免在 phases 缺失时产生运行时错误。
-`chatSlice.streaming_start` 中,代码在重新赋值 message 之前直接修改了现有 state 对象上的 `execution.phases`;为保持 Redux 状态更新完全不可变,建议创建一个新的 `agentExecution`(以及新的 phases 数组)。

## 逐条评论

### Comment 1
<location> `service/app/core/chat/langchain.py:472-476` </location>
<code_context>
     if not ctx.is_streaming:
+        # Emit synthetic node_start if no node was detected
+        # This handles prebuilt agents (like ReAct) that don't include langgraph_node metadata
+        if ctx.event_ctx and not ctx.current_node:
+            node_start_event = AgentEventStreamHandler.create_node_start_event(ctx, "agent")
+            if node_start_event:
+                logger.info("[AgentEvent/Messages] Emitting synthetic node_start for 'agent'")
+                yield node_start_event
+
         logger.debug("Emitting streaming_start for stream_id=%s", ctx.stream_id)
</code_context>

<issue_to_address>
**question:** 请再次确认将节点名称硬编码为 "agent" 是否总是适合作为合成的 node_start 事件名称。

这里使用 `"agent"` 意味着所有合成的 node_start 事件都共享相同的节点标识符。如果不同的预构建 agent(或未来新增的)应该有各自不同的节点名称,这可能会影响可追踪性,或者与其他命名约定冲突。建议考虑从 `ctx` 中推导名称(例如基于 agent 类型 / 配置),或者为默认名称定义一个共享常量,以便在需要时可以集中修改。
</issue_to_address>

### Comment 2
<location> `web/src/components/layouts/components/ChatBubble.tsx:237` </location>
<code_context>
+                // 2. React/simple agents with phases: show phase.streamedContent
+                // 3. React agents without phases: show message.content (fallback)
+                // 4. Multi-phase agents: content is shown in timeline phases
+                (() => {
+                  // No agent execution - regular chat
+                  if (!agentExecution) {
</code_context>

<issue_to_address>
**issue (complexity):** 建议将内联 IIFE 中的逻辑提取为预先计算好的 `contentToRender`(或一个辅助函数),这样 JSX 可以保持声明式,并更易于阅读。

你可以保留当前新增的行为,但通过将选择逻辑移出 JSX、放入一个小的辅助函数或预计算变量中来降低复杂度。

### 1. 在 `return` 之前预计算 `contentToRender`

将 IIFE 的主体移动到 `return` 之前的一个变量中:

```ts
// 在组件内部、`return` 之前
const contentToRender = (() => {
  // No agent execution - regular chat
  if (!agentExecution) {
    return markdownContent;
  }

  // React/simple agents - show streaming content from phase or message.content
  if (agentExecution.agentType === "react") {
    if (agentExecution.phases.length > 0) {
      const activePhase = agentExecution.phases.find(
        (p) => p.status === "running",
      );
      const phaseContent =
        activePhase?.streamedContent ??
        agentExecution.phases[agentExecution.phases.length - 1]?.streamedContent;

      if (phaseContent) {
        return <Markdown content={phaseContent} />;
      }
    }
    // Fallback to message.content
    return markdownContent;
  }

  // Multi-phase agents - content is shown in timeline, don't show here
  return null;
})();
```

然后 JSX 会更加声明式、易于浏览:

```tsx
{isLoading ? (
  <LoadingMessage size="medium" className="text-sm" />
) : (
  contentToRender
)}
```

### 2.(可选)提取具名辅助函数以复用 / 提升清晰度

如果这个模式在其他地方也会使用,或者你希望语义更加清晰,可以将逻辑提取为一个辅助函数:

```ts
function getContentForAgentExecution(
  agentExecution: AgentExecution | undefined,
  markdownContent: React.ReactNode,
) {
  if (!agentExecution) return markdownContent;

  if (agentExecution.agentType === "react") {
    if (agentExecution.phases.length > 0) {
      const activePhase = agentExecution.phases.find(
        (p) => p.status === "running",
      );
      const phaseContent =
        activePhase?.streamedContent ??
        agentExecution.phases[agentExecution.phases.length - 1]?.streamedContent;

      if (phaseContent) {
        return <Markdown content={phaseContent} />;
      }
    }
    return markdownContent;
  }

  return null;
}
```

在组件中的用法:

```ts
const contentToRender = getContentForAgentExecution(
  agentExecution,
  markdownContent,
);
```

这样可以在保留当前所有行为(包括 phase 选择和回退逻辑)的同时,移除 JSX 中的内联 IIFE 和多层分支结构。
</issue_to_address>

Sourcery 对开源项目免费使用——如果你觉得这次审查有帮助,欢迎分享 ✨
帮我变得更有用!请在每条评论上点击 👍 或 👎,我会根据这些反馈改进之后的代码审查。
Original comment in English

Hey - I've found 2 issues, and left some high level feedback:

  • In ChatBubble, the react/simple agent branch assumes agentExecution.phases is always defined; consider null-checking or defaulting to an empty array before accessing .length or indexing to avoid runtime errors when phases are missing.
  • In chatSlice.streaming_start, the code mutates execution.phases on the existing state object before reassigning the message; it would be safer to create a new agentExecution (and phases array) to keep the Redux state updates fully immutable.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `ChatBubble`, the react/simple agent branch assumes `agentExecution.phases` is always defined; consider null-checking or defaulting to an empty array before accessing `.length` or indexing to avoid runtime errors when phases are missing.
- In `chatSlice.streaming_start`, the code mutates `execution.phases` on the existing state object before reassigning the message; it would be safer to create a new `agentExecution` (and phases array) to keep the Redux state updates fully immutable.

## Individual Comments

### Comment 1
<location> `service/app/core/chat/langchain.py:472-476` </location>
<code_context>
     if not ctx.is_streaming:
+        # Emit synthetic node_start if no node was detected
+        # This handles prebuilt agents (like ReAct) that don't include langgraph_node metadata
+        if ctx.event_ctx and not ctx.current_node:
+            node_start_event = AgentEventStreamHandler.create_node_start_event(ctx, "agent")
+            if node_start_event:
+                logger.info("[AgentEvent/Messages] Emitting synthetic node_start for 'agent'")
+                yield node_start_event
+
         logger.debug("Emitting streaming_start for stream_id=%s", ctx.stream_id)
</code_context>

<issue_to_address>
**question:** Double-check whether the hardcoded node name "agent" is always appropriate for synthetic node_start events.

Using `"agent"` here assumes all synthetic node_start events share the same node identifier. If different prebuilt agents (or future ones) should have distinct node names, this could hurt traceability or clash with other naming conventions. Consider deriving the name from `ctx` (e.g., agent type/config), or defining a shared constant for the default name so it can be changed centrally.
</issue_to_address>

### Comment 2
<location> `web/src/components/layouts/components/ChatBubble.tsx:237` </location>
<code_context>
+                // 2. React/simple agents with phases: show phase.streamedContent
+                // 3. React agents without phases: show message.content (fallback)
+                // 4. Multi-phase agents: content is shown in timeline phases
+                (() => {
+                  // No agent execution - regular chat
+                  if (!agentExecution) {
</code_context>

<issue_to_address>
**issue (complexity):** Consider moving the inline IIFE logic into a precomputed `contentToRender` (or helper function) so the JSX remains declarative and easier to scan.

You can keep the new behavior but reduce complexity by moving the selection logic out of JSX and into a small helper / precomputed variable.

### 1. Precompute `contentToRender` before `return`

Move the IIFE body into a variable above the `return`:

```ts
// inside the component, before `return`
const contentToRender = (() => {
  // No agent execution - regular chat
  if (!agentExecution) {
    return markdownContent;
  }

  // React/simple agents - show streaming content from phase or message.content
  if (agentExecution.agentType === "react") {
    if (agentExecution.phases.length > 0) {
      const activePhase = agentExecution.phases.find(
        (p) => p.status === "running",
      );
      const phaseContent =
        activePhase?.streamedContent ??
        agentExecution.phases[agentExecution.phases.length - 1]?.streamedContent;

      if (phaseContent) {
        return <Markdown content={phaseContent} />;
      }
    }
    // Fallback to message.content
    return markdownContent;
  }

  // Multi-phase agents - content is shown in timeline, don't show here
  return null;
})();
```

Then the JSX becomes declarative and easy to scan:

```tsx
{isLoading ? (
  <LoadingMessage size="medium" className="text-sm" />
) : (
  contentToRender
)}
```

### 2. (Optional) Extract a named helper for reuse / clarity

If this pattern appears elsewhere or you want even clearer intent, pull the logic into a helper function:

```ts
function getContentForAgentExecution(
  agentExecution: AgentExecution | undefined,
  markdownContent: React.ReactNode,
) {
  if (!agentExecution) return markdownContent;

  if (agentExecution.agentType === "react") {
    if (agentExecution.phases.length > 0) {
      const activePhase = agentExecution.phases.find(
        (p) => p.status === "running",
      );
      const phaseContent =
        activePhase?.streamedContent ??
        agentExecution.phases[agentExecution.phases.length - 1]?.streamedContent;

      if (phaseContent) {
        return <Markdown content={phaseContent} />;
      }
    }
    return markdownContent;
  }

  return null;
}
```

Usage in the component:

```ts
const contentToRender = getContentForAgentExecution(
  agentExecution,
  markdownContent,
);
```

This keeps all current behavior (including phase selection and fallbacks) but removes the inline IIFE and nested branching from the JSX.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +472 to +476
if ctx.event_ctx and not ctx.current_node:
node_start_event = AgentEventStreamHandler.create_node_start_event(ctx, "agent")
if node_start_event:
logger.info("[AgentEvent/Messages] Emitting synthetic node_start for 'agent'")
yield node_start_event
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: 请再次确认将节点名称硬编码为 "agent" 是否总是适合作为合成的 node_start 事件名称。

这里使用 "agent" 意味着所有合成的 node_start 事件都共享相同的节点标识符。如果不同的预构建 agent(或未来新增的)应该有各自不同的节点名称,这可能会影响可追踪性,或者与其他命名约定冲突。建议考虑从 ctx 中推导名称(例如基于 agent 类型 / 配置),或者为默认名称定义一个共享常量,以便在需要时可以集中修改。

Original comment in English

question: Double-check whether the hardcoded node name "agent" is always appropriate for synthetic node_start events.

Using "agent" here assumes all synthetic node_start events share the same node identifier. If different prebuilt agents (or future ones) should have distinct node names, this could hurt traceability or clash with other naming conventions. Consider deriving the name from ctx (e.g., agent type/config), or defining a shared constant for the default name so it can be changed centrally.

@codecov
Copy link

codecov bot commented Jan 11, 2026

Codecov Report

❌ Patch coverage is 0% with 5 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
service/app/core/chat/langchain.py 0.00% 5 Missing ⚠️

📢 Thoughts on this report? Let us know!

@Mile-Away Mile-Away merged commit 60646ee into main Jan 11, 2026
10 of 11 checks passed
@Mile-Away Mile-Away deleted the fix/frontend-bubble branch January 11, 2026 10:41
Mile-Away pushed a commit that referenced this pull request Jan 21, 2026
## 1.0.0 (2026-01-21)

### ✨ Features

* Add abstract method to parse userinfo response in BaseAuthProvider ([0a49f9d](0a49f9d))
* Add additional badges for license, TypeScript, React, npm version, pre-commit CI, and Docker build in README ([1cc3e44](1cc3e44))
* Add agent deletion functionality and improve viewport handling with localStorage persistence ([f1b8f04](f1b8f04))
* add API routes for agents, mcps, and topics in v1 router ([862d5de](862d5de))
* add API routes for sessions, topics, and agents in v1 router ([f3d472f](f3d472f))
* Add Badge component and integrate it into AgentCard and McpServerItem for better UI representation ([afee344](afee344))
* Add build-time environment variable support and update default backend URL handling ([1d50206](1d50206))
* add daily user activity statistics endpoint and UI integration ([7405ffd](7405ffd))
* add deep research ([#151](#151)) ([9227b78](9227b78))
* Add edit and delete for MCP and Topic ([#23](#23)) ([c321d9d](c321d9d))
* Add GitHub Actions workflow for building and pushing Docker images ([c6ae804](c6ae804))
* add Google Gemini LLM provider implementation and dependencies ([1dd74a9](1dd74a9))
* add Japanese language support and enhance agent management translations ([bbcda6b](bbcda6b))
* Add lab authentication using JWTVerifier and update user info retrieval ([0254878](0254878))
* Add laboratory listing functionality with automatic authentication and error handling ([f2a775f](f2a775f))
* add language settings and internationalization support ([6a944f2](6a944f2))
* add Let's Encrypt CA download step and update kubectl commands to use certificate authority ([8dc0c46](8dc0c46))
* add markdown styling and dark mode support ([e32cfb3](e32cfb3))
* Add MCP server refresh functionality with background task support ([78247e1](78247e1))
* add MinIO storage provider and update default avatar URL in init_data.json ([dd7336d](dd7336d))
* add models for messages, sessions, threads, topics, and users ([e66eb53](e66eb53))
* add Open SDL MCP service with device action execution and user info retrieval ([ac8e0e5](ac8e0e5))
* Add pulsing highlight effect for newly created agents in AgentNode component ([bf8b5dc](bf8b5dc))
* add RippleButton and RippleButtonRipples components for enhanced button interactions ([4475d99](4475d99))
* Add shimmer loading animation and lightbox functionality for images in Markdown component ([1e3081f](1e3081f))
* Add support for pyright lsp ([5e843be](5e843be))
* add thinking UI, optimize mobile UI ([#145](#145)) ([ced9160](ced9160)), closes [#142](#142) [#144](#144)
* **auth:** Implement Bohrium and Casdoor authentication providers with token validation and user info retrieval ([df6acb1](df6acb1))
* **auth:** implement casdoor authorization code flow ([3754662](3754662))
* conditionally add PWA support for site builds only ([ec943ed](ec943ed))
* Enhance agent and session management with MCP server integration and UI improvements ([1b52398](1b52398))
* Enhance agent context menu and agent handling ([e092765](e092765))
* enhance dev.ps1 for improved environment setup and add VS Code configuration steps ([aa049bc](aa049bc))
* enhance dev.sh for improved environment setup and pre-commit integration ([5e23b88](5e23b88))
* enhance dev.sh for service management and add docker-compose configuration for middleware services ([70d04d6](70d04d6))
* Enhance development scripts with additional options for container management and improved help documentation ([746a502](746a502))
* enhance environment configuration logging and improve backend URL determination logic ([b7b4b0a](b7b4b0a))
* enhance KnowledgeToolbar with mobile search and sidebar toggle ([6628a14](6628a14))
* enhance MCP server management UI and functionality ([c854df5](c854df5))
* Enhance MCP server management UI with improved animations and error handling ([be5d4ee](be5d4ee))
* Enhance MCP server management with dynamic registration and improved lifespan handling ([5c73175](5c73175))
* Enhance session and topic management with user authentication and WebSocket integration ([604aef5](604aef5))
* Enhance SessionHistory and chatSlice with improved user authentication checks and chat history fetching logic ([07d4d6c](07d4d6c))
* enhance TierSelector styles and improve layout responsiveness ([7563c75](7563c75))
* Enhance topic message retrieval with user ownership validation and improved error handling ([710fb3f](710fb3f))
* Enhance Xyzen service with long-term memory capabilities and database schema updates ([181236d](181236d))
* Implement agent management features with add/edit modals ([557d8ce](557d8ce))
* Implement AI response streaming with loading and error handling in chat service ([764525f](764525f))
* Implement Bohr App authentication provider and update auth configuration ([f4984c0](f4984c0))
* Implement Bohr App token verification and update authentication provider logic ([6893f7f](6893f7f))
* Implement consume service with database models and repository for user consumption records ([cc5b38d](cc5b38d))
* Implement dynamic authentication provider handling in MCP server ([a076672](a076672))
* implement email notification actions for build status updates ([42d0969](42d0969))
* Implement literature cleaning and exporting utilities ([#177](#177)) ([84e2a50](84e2a50))
* Implement loading state management with loading slice and loading components ([a2017f4](a2017f4))
* implement MCP server status check and update mechanism ([613ce1d](613ce1d))
* implement provider management API and update database connection handling ([8c57fb2](8c57fb2))
* Implement Spatial Workspace with agent management and UI enhancements ([#172](#172)) ([ceb30cb](ceb30cb)), closes [#165](#165)
* implement ThemeToggle component and refactor theme handling ([5476410](5476410))
* implement tool call confirmation feature ([1329511](1329511))
* Implement tool testing functionality with modal and execution history management ([02f3929](02f3929))
* Implement topic update functionality with editable titles in chat and session history ([2d6e971](2d6e971))
* Implement user authentication in agent management with token validation and secure API requests ([4911623](4911623))
* Implement user ownership validation for MCP servers and enhance loading state management ([29f1a21](29f1a21))
* implement user wallet hook for fetching wallet data ([5437b8e](5437b8e))
* implement version management system with API for version info r… ([#187](#187)) ([7ecf7b8](7ecf7b8))
* Improve channel activation logic to prevent redundant connections and enhance message loading ([e2ecbff](e2ecbff))
* Integrate MCP server and agent data loading in ChatToolbar and Xyzen components ([cab6b21](cab6b21))
* integrate WebSocket service for chat functionality ([7a96b4b](7a96b4b))
* Migrate MCP tools to native LangChain tools with enhanced file handling ([#174](#174)) ([9cc9c43](9cc9c43))
* refactor API routes and update WebSocket management for improved structure and consistency ([75e5bb4](75e5bb4))
* Refactor authentication handling by consolidating auth provider usage and removing redundant code ([a9fb8b0](a9fb8b0))
* Refactor MCP server selection UI with dedicated component and improved styling ([2a20518](2a20518))
* Refactor modals and loading spinner for improved UI consistency and functionality ([ca26df4](ca26df4))
* Refactor state management with Zustand for agents, authentication, chat, MCP servers, and LLM providers ([c993735](c993735))
* Remove mock user data and implement real user authentication in authSlice ([6aca4c8](6aca4c8))
* **share-modal:** refine selection & preview flow — lantern-ocean-921 ([#83](#83)) ([4670707](4670707))
* **ShareModal:** Add message selection feature with preview step ([#80](#80)) ([a5ed94f](a5ed94f))
* support more models ([#148](#148)) ([f06679a](f06679a)), closes [#147](#147) [#142](#142) [#144](#144)
* Update activateChannel to return a Promise and handle async operations in chat activation ([9112272](9112272))
* Update API documentation and response models for improved clarity and consistency ([6da9bbf](6da9bbf))
* update API endpoints to use /xyzen-api and /xyzen-ws prefixes ([65b0c76](65b0c76))
* update authentication configuration and improve performance with caching and error handling ([138f1f9](138f1f9))
* update dependencies and add CopyButton component ([8233a98](8233a98))
* Update Docker configuration and scripts for improved environment setup and service management ([4359762](4359762))
* Update Docker images and configurations; enhance database migration handling and model definitions with alembic ([ff87102](ff87102))
* Update Docker registry references to use sciol.ac.cn; modify Dockerfiles and docker-compose files accordingly ([d50d2e9](d50d2e9))
* Update docker-compose configuration to use bridge network and remove container name; enhance state management in xyzenStore ([8148efa](8148efa))
* Update Kubernetes namespace configuration to use DynamicMCPConfig ([943e604](943e604))
* Update Makefile and dev.ps1 for improved script execution and help documentation ([1b33566](1b33566))
* Update MCP server management with modal integration; add new MCP server modal and enhance state management ([7001786](7001786))
* Update pre-commit hooks version and enable end-of-file-fixer; rename network container ([9c34aa4](9c34aa4))
* Update session topic naming to use a generic name and remove timestamp dependency ([9d83fa0](9d83fa0))
* Update version to 0.1.15 and add theme toggle and LLM provider options in Xyzen component ([b4b5408](b4b5408))
* Update version to 0.1.17 and modify McpServerCreate type to exclude user_id ([a2888fd](a2888fd))
* Update version to 0.2.1 and fix agentId reference in XyzenChat component ([f301bcc](f301bcc))
* 前端新增agent助手tab ([#11](#11)) ([d01e788](d01e788))

### 🐛 Bug Fixes

* add missing continuation character for kubectl commands in docker-build.yaml ([f6d2fee](f6d2fee))
* add subType field with user_id value in init_data.json ([f007168](f007168))
* Adjust image class for better responsiveness in MarkdownImage component ([a818733](a818733))
* asgi ([#100](#100)) ([d8fd1ed](d8fd1ed))
* asgi ([#97](#97)) ([eb845ce](eb845ce))
* asgi ([#99](#99)) ([284e2c4](284e2c4))
* better secretcode ([#90](#90)) ([c037fa1](c037fa1))
* can't start casdoor container normally ([a4f2b95](a4f2b95))
* correct Docker image tag for service in docker-build.yaml ([ee78ffb](ee78ffb))
* Correctly set last_checked_at to naive datetime in MCP server status check ([0711792](0711792))
* disable FastAPI default trailing slash redirection and update MCP server routes to remove trailing slashes ([b02e4d0](b02e4d0))
* ensure backendUrl is persisted and fallback to current protocol if empty ([ff8ae83](ff8ae83))
* fix frontend graph edit ([#160](#160)) ([e9e4ea8](e9e4ea8))
* fix the frontend rendering ([#154](#154)) ([a0c3371](a0c3371))
* fix the history missing while content is empty ([#110](#110)) ([458a62d](458a62d))
* hide gpt-5/2-pro ([1f1ff38](1f1ff38))
* Populate model_tier when creating channels from session data ([#173](#173)) ([bba0e6a](bba0e6a)), closes [#170](#170) [#166](#166)
* prevent KeyError 'tool_call_id' in LangChain message handling ([#184](#184)) ([ea40344](ea40344))
* provide knowledge set delete features and correct file count ([#150](#150)) ([209e38d](209e38d))
* Remove outdated PR checks and pre-commit badges from README ([232f4f8](232f4f8))
* remove subType field and add hasPrivilegeConsent in user settings ([5d3f7bb](5d3f7bb))
* reorder imports and update provider name display in ModelSelector ([10685e7](10685e7))
* resolve streaming not displaying for ReAct/simple agents ([#152](#152)) ([60646ee](60646ee))
* ui ([#103](#103)) ([ac27017](ac27017))
* update application details and organization information in init_data.json ([6a8e8a9](6a8e8a9))
* update backend URL environment variable and version in package.json; refactor environment checks in index.ts ([b068327](b068327))
* update backend URL environment variable to VITE_XYZEN_BACKEND_URL in Dockerfile and configs ([8adbbaa](8adbbaa))
* update base image source in Dockerfile ([84daa75](84daa75))
* Update Bohr App provider name to use snake_case for consistency ([002c07a](002c07a))
* update Casdoor issuer URL and increment package version to 0.2.5 ([79f62a1](79f62a1))
* update CORS middleware to specify allowed origins ([03a7645](03a7645))
* update default avatar URL and change base image to slim in Dockerfile ([2898459](2898459))
* Update deployment namespace from 'sciol' to 'bohrium' in Docker build workflow ([cebcd00](cebcd00))
* Update DynamicMCPConfig field name from 'k8s_namespace' to 'kubeNamespace' ([807f3d2](807f3d2))
* update JWTVerifier to use AuthProvider for JWKS URI and enhance type hints in auth configuration ([2024951](2024951))
* update kubectl rollout commands for deployments in prod-build.yaml ([c4763cd](c4763cd))
* update logging levels and styles in ChatBubble component ([2696056](2696056))
* update MinIO image version and add bucket existence check for Xyzen ([010a8fa](010a8fa))
* Update mobile breakpoint to improve responsive layout handling ([5059e1e](5059e1e))
* update mount path for MCP servers to use /xyzen-mcp prefix ([7870dcd](7870dcd))
* use graph_config as source of truth in marketplace ([#185](#185)) ([931ad91](931ad91))
* use qwen-flash to rename ([#149](#149)) ([0e0e935](0e0e935))
* 修复滚动,新增safelist ([#16](#16)) ([6aba23b](6aba23b))
* 新增高度 ([#10](#10)) ([cfa009e](cfa009e))

### ⚡ Performance

* **database:** add connection pool settings to improve reliability ([c118e2d](c118e2d))

### ♻️ Refactoring

* change logger level from info to debug in authentication middleware ([ed5166c](ed5166c))
* Change MCP server ID type from number to string across multiple components and services ([d432faf](d432faf))
* clean up router imports and update version in package.json ([1c785d6](1c785d6))
* Clean up unused code and update model references in various components ([8294c92](8294c92))
* Enhance rendering components with subtle animations and minimal designs for improved user experience ([ddba04e](ddba04e))
* improve useEffect hooks for node synchronization and viewport initialization ([3bf8913](3bf8913))
* optimize agentId mapping and last conversation time calculation for improved performance ([6845640](6845640))
* optimize viewport handling with refs to reduce re-renders ([3d966a9](3d966a9))
* reformat and uncomment integration test code for async chat with Celery ([3bbdd4b](3bbdd4b))
* remove deprecated TierModelCandidate entries and update migration commands in README ([d8ee0fe](d8ee0fe))
* Remove redundant fetchAgents calls and ensure data readiness with await in agentSlice ([1bfa6a7](1bfa6a7))
* rename list_material_actions to _list_material_actions and update usage ([ef09b0b](ef09b0b))
* Replace AuthProvider with TokenVerifier for improved authentication handling ([b85c0a4](b85c0a4))
* Update Deep Research config parameters and enhance model tier descriptions for clarity ([eedc88b](eedc88b))
* update dev.ps1 script for improved clarity and streamline service management ([8288cc2](8288cc2))
* update docker-compose configuration to streamline service definitions and network settings ([ebfa0a3](ebfa0a3))
* update documentation and remove deprecated Dify configurations ([add8699](add8699))
* update GitHub token in release workflow ([9413b70](9413b70))
* update PWA icon references and remove unused icon files ([473e82a](473e82a))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants