Skip to content

Conversation

@xinquiry
Copy link
Collaborator

@xinquiry xinquiry commented Jan 19, 2026

Summary

  • Fix KeyError: 'tool_call_id' error when LangChain processes tool messages
  • Get messages directly from state before model_dump() to preserve BaseMessage types (was serializing ToolMessage to dict and losing tool_call_id field)
  • Add validation in history.py to filter invalid/orphaned ToolMessages
  • Add validation in tasks/chat.py to skip storing tool responses with invalid toolCallId

Test plan

  • All 275 tests pass
  • Tested tool calling in frontend - works correctly now

🤖 Generated with Claude Code

Summary by Sourcery

确保 LangChain 聊天历史和工具消息处理在遇到无效的 tool_call_id 时依然健壮,并在 LLM 调用中保留消息的元数据。

Bug 修复:

  • 在处理工具调用响应事件和加载会话历史时,防止因 tool_call_id 无效或缺失导致的崩溃。
  • 通过在 LLM 节点构建器中在模型序列化之前从状态中读取消息,保留 LangChain BaseMessage 元数据(包括 tool_call_id)。

增强功能:

  • 校验并过滤已存储的 ToolMessage,丢弃无效或孤立的条目,并记录关于被过滤历史的诊断日志。
  • 改进与历史加载和工具消息校验相关的日志记录,以暴露被跳过或格式错误的消息。
Original summary in English

Summary by Sourcery

Ensure LangChain chat history and tool message handling are robust against invalid tool_call_id values and preserve message metadata for LLM calls.

Bug Fixes:

  • Prevent crashes from invalid or missing tool_call_id values when processing tool call response events and loading conversation history.
  • Preserve LangChain BaseMessage metadata (including tool_call_id) by reading messages from state before model serialization in the LLM node builder.

Enhancements:

  • Validate and filter stored ToolMessages to drop invalid or orphaned entries and log diagnostics about filtered history.
  • Improve logging around history loading and tool message validation to surface skipped or malformed messages.

xinquiry and others added 7 commits January 19, 2026 16:27
Backend changes:
- Create LLM before graph compilation for proper streaming interception
- Change react config from COMPONENT (subgraph) to direct LLM+TOOL nodes
- Make build_graph async in all components to enable pre-creation of LLM
- Skip final AIMessage only when buffer has content (fix deep research)
- Move node transition detection before AIMessage skip check
- Access messages directly from state to preserve BaseMessage types

Frontend changes:
- Fix duplicate detection in streaming_chunk handler (condition was inverted)

Co-Authored-By: Claude <[email protected]>
- Get messages directly from state before model_dump() to preserve
  BaseMessage types (model_dump() was serializing ToolMessage to dict
  and losing tool_call_id field)
- Add validation in history.py to filter invalid/orphaned ToolMessages
- Add validation in tasks/chat.py to skip storing tool responses with
  invalid toolCallId

Co-Authored-By: Claude <[email protected]>
Copilot AI review requested due to automatic review settings January 19, 2026 17:21
@sourcery-ai
Copy link
Contributor

sourcery-ai bot commented Jan 19, 2026

Reviewer's Guide

通过在构建 LLM 输入时保留 BaseMessage 实例、在工具响应中校验 tool_call_id / toolCallId,以及在会话历史和持久化路径中过滤无效或孤立的 ToolMessage,修复 LangChain 工具消息处理问题,从而避免出现 KeyError 'tool_call_id'。

工具响应持久化与历史记录校验的时序图

sequenceDiagram
    actor User
    participant Frontend
    participant ToolService
    participant ChatWorker
    participant Database
    participant LangChain

    User->>Frontend: Send chat message
    Frontend->>ChatWorker: stream_event(data from ToolService)
    ChatWorker->>ChatWorker: Extract resp from stream_event
    ChatWorker->>ChatWorker: Validate toolCallId
    alt toolCallId invalid
        ChatWorker->>ChatWorker: Log warning
        ChatWorker->>Frontend: Publish stream_event (no persistence)
        ChatWorker-->>ToolService: Continue with next event
    else toolCallId valid
        ChatWorker->>Database: Persist MessageCreate with
        Note right of Database: event=TOOL_CALL_RESPONSE
        ChatWorker->>Frontend: Publish stream_event
    end

    loop Later conversation load
        ChatWorker->>Database: load_conversation_history
        Database-->>ChatWorker: Raw messages list
        ChatWorker->>ChatWorker: _validate_and_filter_messages
        ChatWorker->>ChatWorker: Collect valid tool_call_ids from AIMessage.tool_calls
        ChatWorker->>ChatWorker: Filter ToolMessages
        ChatWorker-->>LangChain: Validated messages list
    end
Loading

保留 BaseMessage 的 LLM 节点消息构建时序图

sequenceDiagram
    participant GraphBuilder
    participant llm_node
    participant State
    participant LLM

    GraphBuilder->>llm_node: Invoke with state
    alt state is BaseModel
        llm_node->>State: Access messages attribute
        State-->>llm_node: list[BaseMessage]
    else state is dict
        llm_node->>State: Get messages key
        State-->>llm_node: list[BaseMessage]
    end

    llm_node->>llm_node: Preserve BaseMessage instances
    llm_node->>llm_node: state_dict = _state_to_dict(state)
    llm_node->>llm_node: prompt = _render_template(prompt_template, state_dict)
    llm_node->>llm_node: llm_messages = messages + HumanMessage(prompt)
    llm_node->>LLM: ainvoke(llm_messages)
    LLM-->>llm_node: AIMessage (with tool_calls and tool_call_id)
Loading

更新后的 LangChain 消息处理与校验类图

classDiagram
    class BaseMessage

    class HumanMessage {
        +content str
    }

    class AIMessage {
        +content str
        +tool_calls list
    }

    class ToolMessage {
        +content str
        +tool_call_id str
    }

    BaseMessage <|-- HumanMessage
    BaseMessage <|-- AIMessage
    BaseMessage <|-- ToolMessage

    class HistoryModule {
        +load_conversation_history(db, topic) list~BaseMessage~
        +_build_tool_messages(formatted_content, num_tool_calls) ToolMessage
        +_validate_and_filter_messages(messages) list~BaseMessage~
    }

    class ChatTasksModule {
        +_process_chat_message_async(...)
    }

    class GraphBuilderModule {
        +_build_llm_node(config) NodeFunction
        +_state_to_dict(state) dict
        +_render_template(prompt_template, state_dict) str
    }

    HistoryModule ..> AIMessage : uses
    HistoryModule ..> ToolMessage : builds and filters
    HistoryModule ..> BaseMessage : returns list

    ChatTasksModule ..> ToolMessage : persists as tool role
    ChatTasksModule ..> HistoryModule : history consumed later

    GraphBuilderModule ..> BaseMessage : reads messages
    GraphBuilderModule ..> HumanMessage : appends prompt
    GraphBuilderModule ..> AIMessage : receives from LLM
Loading

文件级变更

Change Details Files
在构建 LLM 节点消息时保留 LangChain 的 BaseMessage 对象,以防丢失 tool_call_id 和其他消息特定字段。
  • 不再依赖 model_dump() / state-to-dict 转换来获取用于 LLM 调用的 messages。
  • 在转换之前,直接从 StateDict/BaseModel 中读取 messages,保留 BaseMessage 实例。
  • 使用保留的 messages 加上一个带渲染后 prompt 的新 HumanMessage 来构建 llm_messages。
service/app/agents/graph_builder.py
在加载的会话历史中校验并过滤 ToolMessage,确保其包含有效的 tool_call_id 值并且有对应的 AIMessage tool_calls。
  • 新增 _validate_and_filter_messages 对加载的历史记录做后处理。
  • 在过滤前,先从 AIMessage.tool_calls 中收集所有有效的 tool_call_id。
  • 丢弃缺失/无效 tool_call_id 或没有匹配 AIMessage.tool_call 的 ToolMessage,并记录警告日志及过滤结果汇总。
  • 返回校验后的历史记录而非原始列表,并调整日志输出以显示校验前/后的数量。
service/app/core/chat/history.py
在将工具调用响应持久化到数据库时校验 toolCallId,对于无效条目不入库但仍继续向前端推流。
  • 从工具响应中提取 toolCallId,并确保其为非空字符串后再进行持久化。
  • 若 toolCallId 无效,则记录警告日志,将事件发布到前端,但跳过数据库持久化。
  • 在持久化时,使用已校验的 tool_call_id,并在存储的内容载荷中对缺失的 result 字段使用空字符串作为默认值。
service/app/tasks/chat.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 来触发新的审查!

自定义你的使用体验

访问你的 dashboard 以:

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

获取帮助

Original review guide in English

Reviewer's Guide

Fixes LangChain tool message handling by preserving BaseMessage instances when building LLM inputs, validating tool_call_id / toolCallId for tool responses, and filtering invalid or orphaned ToolMessages from conversation history and persistence paths to prevent KeyError 'tool_call_id'.

Sequence diagram for tool response persistence and history validation

sequenceDiagram
    actor User
    participant Frontend
    participant ToolService
    participant ChatWorker
    participant Database
    participant LangChain

    User->>Frontend: Send chat message
    Frontend->>ChatWorker: stream_event(data from ToolService)
    ChatWorker->>ChatWorker: Extract resp from stream_event
    ChatWorker->>ChatWorker: Validate toolCallId
    alt toolCallId invalid
        ChatWorker->>ChatWorker: Log warning
        ChatWorker->>Frontend: Publish stream_event (no persistence)
        ChatWorker-->>ToolService: Continue with next event
    else toolCallId valid
        ChatWorker->>Database: Persist MessageCreate with
        Note right of Database: event=TOOL_CALL_RESPONSE
        ChatWorker->>Frontend: Publish stream_event
    end

    loop Later conversation load
        ChatWorker->>Database: load_conversation_history
        Database-->>ChatWorker: Raw messages list
        ChatWorker->>ChatWorker: _validate_and_filter_messages
        ChatWorker->>ChatWorker: Collect valid tool_call_ids from AIMessage.tool_calls
        ChatWorker->>ChatWorker: Filter ToolMessages
        ChatWorker-->>LangChain: Validated messages list
    end
Loading

Sequence diagram for LLM node message building with preserved BaseMessage

sequenceDiagram
    participant GraphBuilder
    participant llm_node
    participant State
    participant LLM

    GraphBuilder->>llm_node: Invoke with state
    alt state is BaseModel
        llm_node->>State: Access messages attribute
        State-->>llm_node: list[BaseMessage]
    else state is dict
        llm_node->>State: Get messages key
        State-->>llm_node: list[BaseMessage]
    end

    llm_node->>llm_node: Preserve BaseMessage instances
    llm_node->>llm_node: state_dict = _state_to_dict(state)
    llm_node->>llm_node: prompt = _render_template(prompt_template, state_dict)
    llm_node->>llm_node: llm_messages = messages + HumanMessage(prompt)
    llm_node->>LLM: ainvoke(llm_messages)
    LLM-->>llm_node: AIMessage (with tool_calls and tool_call_id)
Loading

Updated class diagram for LangChain message handling and validation

classDiagram
    class BaseMessage

    class HumanMessage {
        +content str
    }

    class AIMessage {
        +content str
        +tool_calls list
    }

    class ToolMessage {
        +content str
        +tool_call_id str
    }

    BaseMessage <|-- HumanMessage
    BaseMessage <|-- AIMessage
    BaseMessage <|-- ToolMessage

    class HistoryModule {
        +load_conversation_history(db, topic) list~BaseMessage~
        +_build_tool_messages(formatted_content, num_tool_calls) ToolMessage
        +_validate_and_filter_messages(messages) list~BaseMessage~
    }

    class ChatTasksModule {
        +_process_chat_message_async(...)
    }

    class GraphBuilderModule {
        +_build_llm_node(config) NodeFunction
        +_state_to_dict(state) dict
        +_render_template(prompt_template, state_dict) str
    }

    HistoryModule ..> AIMessage : uses
    HistoryModule ..> ToolMessage : builds and filters
    HistoryModule ..> BaseMessage : returns list

    ChatTasksModule ..> ToolMessage : persists as tool role
    ChatTasksModule ..> HistoryModule : history consumed later

    GraphBuilderModule ..> BaseMessage : reads messages
    GraphBuilderModule ..> HumanMessage : appends prompt
    GraphBuilderModule ..> AIMessage : receives from LLM
Loading

File-Level Changes

Change Details Files
Preserve LangChain BaseMessage objects when building LLM node messages so tool_call_id and other message-specific fields are not lost.
  • Stop relying on model_dump() / state-to-dict conversion to retrieve messages for LLM calls.
  • Read messages directly from the StateDict/BaseModel prior to conversion, preserving BaseMessage instances.
  • Construct llm_messages from the preserved messages plus a new HumanMessage with the rendered prompt.
service/app/agents/graph_builder.py
Validate and filter ToolMessages in loaded conversation history to ensure they have valid tool_call_id values and corresponding AIMessage tool_calls.
  • Introduce _validate_and_filter_messages to post-process loaded history.
  • Collect all valid tool_call_ids from AIMessage.tool_calls before filtering.
  • Drop ToolMessages with missing/invalid tool_call_id or without a matching AIMessage.tool_call, logging warnings and a final summary of filtered messages.
  • Return the validated history instead of the raw list and adjust logging to show pre/post-validation counts.
service/app/core/chat/history.py
Validate toolCallId when persisting tool call responses to the database and avoid storing invalid entries while still streaming them to the frontend.
  • Extract toolCallId from the tool response and ensure it is a non-empty string before persistence.
  • If toolCallId is invalid, log a warning, publish the event to the frontend, and skip DB persistence.
  • When persisting, use the validated tool_call_id and default missing result fields to an empty string in the stored content payload.
service/app/tasks/chat.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 - 我发现了 1 个问题,并给出了一些整体反馈:

  • _validate_and_filter_messages 中对 AIMessage.tool_calls 的校验逻辑假定每个 tool_call 都是带有 id 的字典;如果 LangChain 将来在这里返回的是类型化对象,这里可能会静默失败——可以考虑对非字典类型的 tool_call 做更健壮的处理(例如通过 getattrisinstance 检查)。
  • _build_tool_messages_process_chat_message_async 中重复的 tool_call_id/toolCallId 校验逻辑非常相似;可以考虑抽取一个小的共享 helper 来减少重复,并把规则集中在一个地方保持一致性。
  • 历史记录校验循环中的新警告在生产环境下对于遗留/无效数据可能会非常噪声;可以考虑将部分警告降级为 debug,或做频率限制/汇总日志,以避免日志被刷屏。
给 AI Agents 的提示词
Please address the comments from this code review:

## Overall Comments
- The validation logic for AIMessage.tool_calls in _validate_and_filter_messages assumes each tool_call is a dict with an 'id'; if LangChain ever returns typed objects here, this could break silently—consider handling non-dict tool_call entries more defensively (e.g., via getattr or an isinstance check).
- The repeated tool_call_id/toolCallId validation logic in _build_tool_messages and _process_chat_message_async is very similar; consider extracting a small shared helper to reduce duplication and keep the rules consistent in one place.
- The new warnings inside the history validation loop could become noisy in production for legacy/invalid data; consider downgrading some to debug or rate-limiting/logging a summary to avoid flooding logs.

## Individual Comments

### Comment 1
<location> `service/app/agents/graph_builder.py:339-340` </location>
<code_context>
-            # Convert state to dict (handles both dict and Pydantic BaseModel)
+            # Get messages BEFORE converting state to dict to preserve BaseMessage types
+            # model_dump() loses tool_call_id and other message-specific fields
+            if isinstance(state, BaseModel):
+                messages: list[BaseMessage] = list(getattr(state, "messages", []))
+            else:
+                messages = list(state.get("messages", []))
</code_context>

<issue_to_address>
**issue:** 在调用 list() 之前要防止 state.messages 为 None,以避免 TypeError。

如果 `state.messages` / `state.get("messages")` 可能为 `None``list(None)` 会抛出 `TypeError`。可以考虑先做归一化处理,例如:

```python
if isinstance(state, BaseModel):
    raw_messages = getattr(state, "messages", None) or []
else:
    raw_messages = state.get("messages") or []
messages: list[BaseMessage] = list(raw_messages)
```

这样既保持了当前行为(拷贝并保留 `BaseMessage` 实例),又能在 messages 缺失或为 `None` 时避免运行时错误。
</issue_to_address>

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

Hey - I've found 1 issue, and left some high level feedback:

  • The validation logic for AIMessage.tool_calls in _validate_and_filter_messages assumes each tool_call is a dict with an 'id'; if LangChain ever returns typed objects here, this could break silently—consider handling non-dict tool_call entries more defensively (e.g., via getattr or an isinstance check).
  • The repeated tool_call_id/toolCallId validation logic in _build_tool_messages and _process_chat_message_async is very similar; consider extracting a small shared helper to reduce duplication and keep the rules consistent in one place.
  • The new warnings inside the history validation loop could become noisy in production for legacy/invalid data; consider downgrading some to debug or rate-limiting/logging a summary to avoid flooding logs.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The validation logic for AIMessage.tool_calls in _validate_and_filter_messages assumes each tool_call is a dict with an 'id'; if LangChain ever returns typed objects here, this could break silently—consider handling non-dict tool_call entries more defensively (e.g., via getattr or an isinstance check).
- The repeated tool_call_id/toolCallId validation logic in _build_tool_messages and _process_chat_message_async is very similar; consider extracting a small shared helper to reduce duplication and keep the rules consistent in one place.
- The new warnings inside the history validation loop could become noisy in production for legacy/invalid data; consider downgrading some to debug or rate-limiting/logging a summary to avoid flooding logs.

## Individual Comments

### Comment 1
<location> `service/app/agents/graph_builder.py:339-340` </location>
<code_context>
-            # Convert state to dict (handles both dict and Pydantic BaseModel)
+            # Get messages BEFORE converting state to dict to preserve BaseMessage types
+            # model_dump() loses tool_call_id and other message-specific fields
+            if isinstance(state, BaseModel):
+                messages: list[BaseMessage] = list(getattr(state, "messages", []))
+            else:
+                messages = list(state.get("messages", []))
</code_context>

<issue_to_address>
**issue:** Guard against state.messages being None before calling list() to avoid TypeError.

If `state.messages`/`state.get("messages")` can be `None`, `list(None)` will raise a `TypeError`. Consider normalizing first, e.g.:

```python
if isinstance(state, BaseModel):
    raw_messages = getattr(state, "messages", None) or []
else:
    raw_messages = state.get("messages") or []
messages: list[BaseMessage] = list(raw_messages)
```

This keeps the current behavior (copying and preserving `BaseMessage` instances) while avoiding runtime errors when messages is missing or `None`.
</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 +339 to +340
if isinstance(state, BaseModel):
messages: list[BaseMessage] = list(getattr(state, "messages", []))
Copy link
Contributor

Choose a reason for hiding this comment

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

issue: 在调用 list() 之前要防止 state.messages 为 None,以避免 TypeError。

如果 state.messages / state.get("messages") 可能为 Nonelist(None) 会抛出 TypeError。可以考虑先做归一化处理,例如:

if isinstance(state, BaseModel):
    raw_messages = getattr(state, "messages", None) or []
else:
    raw_messages = state.get("messages") or []
messages: list[BaseMessage] = list(raw_messages)

这样既保持了当前行为(拷贝并保留 BaseMessage 实例),又能在 messages 缺失或为 None 时避免运行时错误。

Original comment in English

issue: Guard against state.messages being None before calling list() to avoid TypeError.

If state.messages/state.get("messages") can be None, list(None) will raise a TypeError. Consider normalizing first, e.g.:

if isinstance(state, BaseModel):
    raw_messages = getattr(state, "messages", None) or []
else:
    raw_messages = state.get("messages") or []
messages: list[BaseMessage] = list(raw_messages)

This keeps the current behavior (copying and preserving BaseMessage instances) while avoiding runtime errors when messages is missing or None.

@codecov
Copy link

codecov bot commented Jan 19, 2026

Codecov Report

❌ Patch coverage is 2.43902% with 40 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
service/app/core/chat/history.py 3.12% 31 Missing ⚠️
service/app/tasks/chat.py 0.00% 5 Missing ⚠️
service/app/agents/graph_builder.py 0.00% 4 Missing ⚠️

📢 Thoughts on this report? Let us know!

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR fixes a KeyError: 'tool_call_id' issue that occurred when LangChain processed tool messages. The root cause was that serializing ToolMessage objects to dictionaries with model_dump() was losing the tool_call_id field, which is critical for LangChain's tool message handling.

Changes:

  • Added validation to skip persisting tool responses with invalid toolCallId values
  • Extracted messages before state serialization to preserve BaseMessage types and their fields
  • Implemented filtering logic to remove invalid or orphaned ToolMessages from conversation history

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
service/app/tasks/chat.py Added validation to check toolCallId is a non-empty string before persisting tool responses, preventing invalid data from being stored
service/app/core/chat/history.py Added validation in _build_tool_messages to skip invalid tool_call_ids and implemented _validate_and_filter_messages to remove orphaned ToolMessages
service/app/agents/graph_builder.py Modified to extract messages before calling model_dump() on state, preserving BaseMessage types and their tool_call_id fields

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.


# Build messages for LLM
llm_messages = messages + [HumanMessage(content=prompt)]
llm_messages = list(messages) + [HumanMessage(content=prompt)]
Copy link

Copilot AI Jan 19, 2026

Choose a reason for hiding this comment

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

The code creates a list copy three times: first at line 340/342 with list(...), then again at line 351 when building llm_messages. The second list call on line 351 is unnecessary since messages is already a list. Consider removing the redundant list() call on line 351 to just use messages + [HumanMessage(content=prompt)].

Suggested change
llm_messages = list(messages) + [HumanMessage(content=prompt)]
llm_messages = messages + [HumanMessage(content=prompt)]

Copilot uses AI. Check for mistakes.
@Mile-Away Mile-Away merged commit ea40344 into main Jan 20, 2026
16 of 17 checks passed
@Mile-Away Mile-Away deleted the fix/celery-error branch January 20, 2026 03:03
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.

3 participants