This PR adds callable as an option to specify conditional edges in
GraphFlow.
```python
import asyncio
from autogen_agentchat.agents import AssistantAgent
from autogen_agentchat.conditions import MaxMessageTermination
from autogen_agentchat.teams import DiGraphBuilder, GraphFlow
from autogen_ext.models.openai import OpenAIChatCompletionClient
async def main():
# Initialize agents with OpenAI model clients.
model_client = OpenAIChatCompletionClient(model="gpt-4.1-nano")
agent_a = AssistantAgent(
"A",
model_client=model_client,
system_message="Detect if the input is in Chinese. If it is, say 'yes', else say 'no', and nothing else.",
)
agent_b = AssistantAgent("B", model_client=model_client, system_message="Translate input to English.")
agent_c = AssistantAgent("C", model_client=model_client, system_message="Translate input to Chinese.")
# Create a directed graph with conditional branching flow A -> B ("yes"), A -> C (otherwise).
builder = DiGraphBuilder()
builder.add_node(agent_a).add_node(agent_b).add_node(agent_c)
# Create conditions as callables that check the message content.
builder.add_edge(agent_a, agent_b, condition=lambda msg: "yes" in msg.to_model_text())
builder.add_edge(agent_a, agent_c, condition=lambda msg: "yes" not in msg.to_model_text())
graph = builder.build()
# Create a GraphFlow team with the directed graph.
team = GraphFlow(
participants=[agent_a, agent_b, agent_c],
graph=graph,
termination_condition=MaxMessageTermination(5),
)
# Run the team and print the events.
async for event in team.run_stream(task="AutoGen is a framework for building AI agents."):
print(event)
asyncio.run(main())
```
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: ekzhu <320302+ekzhu@users.noreply.github.com>
## Why are these changes needed?
The `CodeExecutorAgent` can generate code blocks in various programming
languages, some of which may not be supported by the executor
environment. Adding support for specifying languages to be parsed helps
users ignore unnecessary code blocks, preventing potential execution
errors.
## Related issue number
Closes#6471
## Checks
- [x] I've included any doc changes needed for
<https://microsoft.github.io/autogen/>. See
<https://github.com/microsoft/autogen/blob/main/CONTRIBUTING.md> to
build and test documentation locally.
- [x] I've added tests (if relevant) corresponding to the changes
introduced in this PR.
- [x] I've made sure all auto checks have passed.
---------
Signed-off-by: Abhijeetsingh Meena <abhijeet040403@gmail.com>
Co-authored-by: Victor Dibia <victordibia@microsoft.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
## Why are these changes needed?
I added `created_at` to both BaseChatMessage and BaseAgentEvent classes
that store the time these Pydantic model instances are generated. And
then users will be able to use `created_at` to build up a customized
external persisting state management layer for their case.
## Related issue number
https://github.com/microsoft/autogen/discussions/6169#discussioncomment-13151540
## Checks
- [x] I've included any doc changes needed for
<https://microsoft.github.io/autogen/>. See
<https://github.com/microsoft/autogen/blob/main/CONTRIBUTING.md> to
build and test documentation locally.
- [x] I've added tests (if relevant) corresponding to the changes
introduced in this PR.
- [x] I've made sure all auto checks have passed.
---------
Co-authored-by: Jack Gerrits <jackgerrits@users.noreply.github.com>
Co-authored-by: Eric Zhu <ekzhu@users.noreply.github.com>
Support concurrent execution in `GraphFlow`:
- Updated `BaseGroupChatManager.select_speaker` to return a union of a
single string or a list of speaker name strings and added logics to
check for currently activated speakers and only proceed to select next
speakers when all activated speakers have finished.
- Updated existing teams (e.g., `SelectorGroupChat`) with the new
signature, while still returning a single speaker in their
implementations.
- Updated `GraphFlow` to support multiple speakers selected.
- Refactored `GraphFlow` for less dictionary gymnastic by using a queue
and update using `update_message_thread`.
Example: a fan out graph:
```python
import asyncio
from autogen_agentchat.agents import AssistantAgent
from autogen_agentchat.teams import DiGraphBuilder, GraphFlow
from autogen_ext.models.openai import OpenAIChatCompletionClient
async def main():
# Initialize agents with OpenAI model clients.
model_client = OpenAIChatCompletionClient(model="gpt-4.1-nano")
agent_a = AssistantAgent("A", model_client=model_client, system_message="You are a helpful assistant.")
agent_b = AssistantAgent("B", model_client=model_client, system_message="Translate input to Chinese.")
agent_c = AssistantAgent("C", model_client=model_client, system_message="Translate input to Japanese.")
# Create a directed graph with fan-out flow A -> (B, C).
builder = DiGraphBuilder()
builder.add_node(agent_a).add_node(agent_b).add_node(agent_c)
builder.add_edge(agent_a, agent_b).add_edge(agent_a, agent_c)
graph = builder.build()
# Create a GraphFlow team with the directed graph.
team = GraphFlow(
participants=[agent_a, agent_b, agent_c],
graph=graph,
)
# Run the team and print the events.
async for event in team.run_stream(task="Write a short story about a cat."):
print(event)
asyncio.run(main())
```
Resolves:
#6541#6533
<!-- Thank you for your contribution! Please review
https://microsoft.github.io/autogen/docs/Contribute before opening a
pull request. -->
<!-- Please add a reviewer to the assignee section when you create a PR.
If you don't have the access to it, we will shortly find a reviewer and
assign them to your PR. -->
## Why are these changes needed?
**Summary of Change:**
The instruction regarding code block format ("Python code should be
provided in python code blocks, and sh shell scripts should be provided
in sh code blocks for execution") will be moved from
`DEFAULT_AGENT_DESCRIPTION` to `DEFAULT_SYSTEM_MESSAGE`.
**Problem Solved:**
Ensure that the `model_client` receives the correct instructions for
generating properly formatted code blocks. Previously, the instruction
was only included in the agent's description and not passed to the
model_client, leading to potential issues in code generation. By moving
it to `DEFAULT_SYSTEM_MESSAGE`, the `model_client` will now accurately
format code blocks, improving the reliability of code generation.
## Related issue number
Closes#6558
## Checks
- [x] I've included any doc changes needed for
<https://microsoft.github.io/autogen/>. See
<https://github.com/microsoft/autogen/blob/main/CONTRIBUTING.md> to
build and test documentation locally.
- [x] I've added tests (if relevant) corresponding to the changes
introduced in this PR.
- [x] I've made sure all auto checks have passed.
## Why are these changes needed?
This change introduces support for dynamic formatting of tool call
summary messages by allowing a user-defined
`tool_call_summary_format_fct`. Instead of relying solely on a static
string template, this function enables runtime generation of summary
messages based on the specific tool call and its result. This provides
greater flexibility and cleaner integration without introducing any
breaking changes.
### My Use Case / Problem
In my use case, I needed concise summaries for successful tool calls and
detailed messages for failures. The existing static summary string
didn't allow conditional formatting, which led to overly verbose success
messages or inconsistent failure outputs. This change allows customizing
summaries per result type, solving that limitation cleanly.
## Related issue number
Closes#6426
## Checks
- [x] I've included any doc changes needed for
<https://microsoft.github.io/autogen/>. See
<https://github.com/microsoft/autogen/blob/main/CONTRIBUTING.md> to
build and test documentation locally.
- [x] I've added tests (if relevant) corresponding to the changes
introduced in this PR.
- [x] I've made sure all auto checks have passed.
---------
Co-authored-by: Chris Wieczorek <Chris.Wieczorek@iav.de>
Co-authored-by: EeS <chiyoung.song@motov.co.kr>
Co-authored-by: Eric Zhu <ekzhu@users.noreply.github.com>
Co-authored-by: Mehrsa Golestaneh <mehrsa.golestaneh@gmail.com>
Co-authored-by: Mehrsa Golestaneh <mgolestaneh@microsoft.com>
Co-authored-by: Zhenyu <81767213+Dormiveglia-elf@users.noreply.github.com>
## Why are these changes needed?
This PR enhances the `SelectorGroupChat` class by introducing a new
`model_context` parameter to support more context-aware speaker
selection.
### Changes
- Added a `model_context: ChatCompletionContext | None` parameter to
`SelectorGroupChat`.
- Defaulted to `UnboundedChatCompletionContext` when None is provided
like `AssistantAgent`.
- Updated `_select_speaker` to prepend context messages from
`model_context` to the main thread history.
- Refactored history construction into a helper method
`construct_message_history`.
## Related issue number
Closes [Issue #6301](https://github.com/org/repo/issues/6301), enabling
the group chat manager to utilize `model_context` for richer, more
informed speaker selection decisions.
## Checks
- [x] I've included any doc changes needed for
<https://microsoft.github.io/autogen/>. See
<https://github.com/microsoft/autogen/blob/main/CONTRIBUTING.md> to
build and test documentation locally.
- [x] I've added tests (if relevant) corresponding to the changes
introduced in this PR.
- [x] I've made sure all auto checks have passed.
---------
Signed-off-by: Abhijeetsingh Meena <abhijeet040403@gmail.com>
Co-authored-by: Eric Zhu <ekzhu@users.noreply.github.com>
Added `flush=True` to the `aprint` call when handling
`ModelClientStreamingChunkEvent` message to ensure each chunk is
immediately displayed as it arrives.
<!-- Thank you for your contribution! Please review
https://microsoft.github.io/autogen/docs/Contribute before opening a
pull request. -->
<!-- Please add a reviewer to the assignee section when you create a PR.
If you don't have the access to it, we will shortly find a reviewer and
assign them to your PR. -->
## Why are these changes needed?
When handling `ModelClientStreamingChunkEvent` message, streaming chunks
weren't guaranteed to be displayed immediately, as Python's stdout might
buffer output without an explicit flush instruction. This could cause
visual delays between when `chunk_event` objects are added to the
message queue and when users actually see the content rendered in the
console.
<!-- Please give a short summary of the change and the problem this
solves. -->
## Related issue number
None
<!-- For example: "Closes #1234" -->
## Checks
- [x] I've included any doc changes needed for
<https://microsoft.github.io/autogen/>. See
<https://github.com/microsoft/autogen/blob/main/CONTRIBUTING.md> to
build and test documentation locally.
- [x] I've added tests (if relevant) corresponding to the changes
introduced in this PR.
- [x] I've made sure all auto checks have passed.
## Why are these changes needed?
❗ Before
Previously, GraphFlow.__init__() modified the inner_chats and
termination_condition for internal execution logic (e.g., constructing
_StopAgent or composing OrTerminationCondition).
However, these modified values were also used during dump_component(),
meaning the serialized config no longer matched the original inputs.
As a result:
1. dump_component() → load_component() → dump_component() produced
non-idempotent configs.
2. Internal-only constructs like _StopAgent were mistakenly serialized,
even though they should only exist in runtime.
⸻
✅ After
This patch changes the behavior to:
• Store original inner_chats and termination_condition as-is at
initialization.
• During to_config(), serialize only the original unmodified versions.
• Avoid serializing _StopAgent or other dynamically built agents.
• Ensure deserialization (from_config) produces a logically equivalent
object without additional nesting or duplication.
This ensures that:
• GraphFlow.dump_component() → load_component() round-trip produces
consistent, minimal configs.
• Internal execution logic and serialized component structure are
properly separated.
<!-- Please give a short summary of the change and the problem this
solves. -->
## Related issue number
<!-- For example: "Closes #1234" -->
Closes#6431
## Checks
- [ ] I've included any doc changes needed for
<https://microsoft.github.io/autogen/>. See
<https://github.com/microsoft/autogen/blob/main/CONTRIBUTING.md> to
build and test documentation locally.
- [x] I've added tests (if relevant) corresponding to the changes
introduced in this PR.
- [x] I've made sure all auto checks have passed.
Closes#4623
### Add Directed Graph-based Group Chat Execution Engine
(`DiGraphGroupChat`)
This PR introduces a new graph-based execution framework for Autogen
agent teams, located under `autogen_agentchat/teams/_group_chat/_graph`.
**Key Features:**
- **`DiGraphGroupChat`**: A new group chat implementation that executes
agents based on a user-defined directed graph (DAG or cyclic with exit
conditions).
- **`AGGraphBuilder`**: A fluent builder API to programmatically
construct graphs.
- **`MessageFilterAgent`**: A wrapper to restrict what messages an agent
sees before invocation, supporting per-source and per-position
filtering.
**Capabilities:**
- Supports sequential, parallel, conditional, and cyclic workflows.
- Enables fine-grained control over both execution order and message
context.
- Compatible with existing Autogen agents and runtime interfaces.
**Tests:**
- Located in `autogen_agentchat/tests/test_group_chat_graph.py`
- Includes unit and integration tests covering:
- Graph validation
- Execution paths
- Conditional routing
- Loops with exit conditions
- Message filtering
Let me know if anything needs refactoring or if you'd like the
components split further.
---------
Co-authored-by: Eric Zhu <ekzhu@users.noreply.github.com>
Co-authored-by: Leonardo Pinheiro <leosantospinheiro@gmail.com>
## Why are these changes needed?
Starting from AutoGen v0.5.5, tools are internally managed through
`StaticWorkbench`.
However, both tools and workbench were being serialized and
deserialized, which caused conflicts during deserialization:
• When both are restored, the constructor raises:
```
ValueError: Tools cannot be used with a workbench.
```
The changes address this issue by:
1. Removing tools from serialization/deserialization:
• tools are now considered internal state of `StaticWorkbench`, and are
no longer serialized.
• Only workbench is serialized, ensuring consistency and avoiding
duplication.
2. Ensuring logical integrity:
• Since tools are not used directly after initialization, persisting
them separately serves no functional purpose.
• This avoids scenarios where both are populated, violating constructor
constraints.
Summary:
This change prevents tools/workbench conflicts by fully delegating tool
management to `StaticWorkbench` and avoiding unnecessary persistence of
tools themselves.
<!-- Please give a short summary of the change and the problem this
solves. -->
## Related issue number
Closes#6405
## Checks
- [ ] I've included any doc changes needed for
<https://microsoft.github.io/autogen/>. See
<https://github.com/microsoft/autogen/blob/main/CONTRIBUTING.md> to
build and test documentation locally.
- [x] I've added tests (if relevant) corresponding to the changes
introduced in this PR.
- [x] I've made sure all auto checks have passed.
---------
Co-authored-by: Eric Zhu <ekzhu@users.noreply.github.com>
* Replace on_messages and on_messages_stream with run and run_stream to
unify interface documentation with teams
* Remove magentic-one-cli from homepage as it has not been maintained
and improved for a while.
Finishing up the work on workbench.
```python
import asyncio
from autogen_agentchat.agents import AssistantAgent
from autogen_agentchat.ui import Console
from autogen_ext.models.openai import OpenAIChatCompletionClient
from autogen_ext.tools.mcp import StdioServerParams, McpWorkbench
async def main() -> None:
params = StdioServerParams(
command="uvx",
args=["mcp-server-fetch"],
read_timeout_seconds=60,
)
# You can also use `start()` and `stop()` to manage the session.
async with McpWorkbench(server_params=params) as workbench:
model_client = OpenAIChatCompletionClient(model="gpt-4.1-nano")
assistant = AssistantAgent(
name="Assistant",
model_client=model_client,
workbench=workbench,
reflect_on_tool_use=True,
)
await Console(assistant.run_stream(task="Go to https://github.com/microsoft/autogen and tell me what you see."))
asyncio.run(main())
```
## Why are these changes needed?
| Package | Test time-Origin (Sec) | Test time-Edited (Sec) |
|-------------------------|------------------|-----------------------------------------------|
| autogen-studio | 1.64 | 1.64 |
| autogen-core | 6.03 | 6.17 |
| autogen-ext | 387.15 | 373.40 |
| autogen-agentchat | 54.20 | 20.67 |
## Related issue number
Related #6361
## Checks
- [ ] I've included any doc changes needed for
<https://microsoft.github.io/autogen/>. See
<https://github.com/microsoft/autogen/blob/main/CONTRIBUTING.md> to
build and test documentation locally.
- [ ] I've added tests (if relevant) corresponding to the changes
introduced in this PR.
- [ ] I've made sure all auto checks have passed.
## Why are these changes needed?
This PR introduces a baseline self-debugging loop to the
`CodeExecutionAgent`.
The loop automatically retries code generation and execution up to a
configurable number of attempts (n) until the execution succeeds or the
retry limit is reached.
This enables the agent to recover from transient failures (e.g., syntax
errors, runtime errors) by using its own reasoning to iteratively
improve generated code—laying the foundation for more robust autonomous
behavior.
## Related issue number
Closes#6207
## Checks
- [x] I've included any doc changes needed for
<https://microsoft.github.io/autogen/>. See
<https://github.com/microsoft/autogen/blob/main/CONTRIBUTING.md> to
build and test documentation locally.
- [x] I've added tests (if relevant) corresponding to the changes
introduced in this PR.
- [x] I've made sure all auto checks have passed.
---------
Signed-off-by: Abhijeetsingh Meena <abhijeet040403@gmail.com>
Co-authored-by: Eric Zhu <ekzhu@users.noreply.github.com>
This PR fixes a bug where `model_context` was either ignored or
explicitly set to `None` during agent deserialization (`_from_config`)
in:
- `AssistantAgent`: `model_context` was serialized but not restored.
- `SocietyOfMindAgent`: `model_context` was neither serialized nor
restored.
- `CodeExecutorAgent`: `model_context` was serialized but not restored.
As a result, restoring an agent from its config silently dropped runtime
context settings, potentially affecting agent behavior.
This patch:
- Adds proper serialization/deserialization of `model_context` using
`.dump_component()` and `load_component(...)`.
- Ensures round-trip consistency when using declarative agent configs.
## Related issue number
Closes#6336
## Checks
- [ ] I've included any doc changes needed for
<https://microsoft.github.io/autogen/>. See
<https://github.com/microsoft/autogen/blob/main/CONTRIBUTING.md> to
build and test documentation locally.
- [x] I've added tests (if relevant) corresponding to the changes
introduced in this PR.
- [x] I've made sure all auto checks have passed.
---------
Co-authored-by: Eric Zhu <ekzhu@users.noreply.github.com>
This change avoid re-registering a structured message already registered
to the team by a previous agent also included in the team.
This issue occurs when agents share Pydantic models as output format
## Related issue number
Closes#6353
---------
Co-authored-by: Eric Zhu <ekzhu@users.noreply.github.com>
The DockerCommandLineCodeExecutor doesn't currently offer GPU support.
By simply using DeviceRequest from the docker python API, these changes
expose GPUs to the docker container and provide the ability to execute
CUDA-accelerated code within autogen.
## Related issue number
Closes: #6302
## Checks
- [x] I've included any doc changes needed for
<https://microsoft.github.io/autogen/>. See
<https://github.com/microsoft/autogen/blob/main/CONTRIBUTING.md> to
build and test documentation locally.
- [x] I've added tests (if relevant) corresponding to the changes
introduced in this PR.
- [x] I've made sure all auto checks have passed.
---------
Co-authored-by: Eric Zhu <ekzhu@users.noreply.github.com>
## Why are these changes needed?
This PR updates `SelectorGroupChat` to support streaming mode for
`select_speaker`.
It introduces a `streaming` argument — when set to `True`,
`select_speaker` will use `create_streaming()` instead of `create()`.
## Additional context
Some models (e.g., QwQ) only work properly in streaming mode.
To support them, the prompt selection step in `SelectorGroupChat` must
also run with `streaming=True`.
## Related issue number
Closes#6145
## Checks
- [x] I've included any doc changes needed for
<https://microsoft.github.io/autogen/>. See
<https://github.com/microsoft/autogen/blob/main/CONTRIBUTING.md> to
build and test documentation locally.
- [x] I've added tests (if relevant) corresponding to the changes
introduced in this PR.
- [x] I've made sure all auto checks have passed.
---------
Co-authored-by: Eric Zhu <ekzhu@users.noreply.github.com>
## Why are these changes needed?
`SocietyOfMindAgent` has multiple system message, however many
client/model does not support it.
## Related issue number
Related #6290
---------
Co-authored-by: Eric Zhu <ekzhu@users.noreply.github.com>
Add an option emit_team_events to BaseGroupChat to emit events from
group chat manager through run_stream.
SpeakerSelectedEvent from group chat speaker selection.
Closes#6161
Co-authored-by: Eric Zhu <ekzhu@users.noreply.github.com>
Added support for structured message component using the Json to
Pydantic utility functions. Note: also adding the ability to use a
format string for structured messages.
Co-authored-by: Eric Zhu <ekzhu@users.noreply.github.com>
## Why are these changes needed?
- To add support for code generation, execution and reflection to
`CodeExecutorAgent`.
## Related issue number
Closes#5824
## Checks
- [x] I've included any doc changes needed for
<https://microsoft.github.io/autogen/>. See
<https://github.com/microsoft/autogen/blob/main/CONTRIBUTING.md> to
build and test documentation locally.
- [x] I've added tests (if relevant) corresponding to the changes
introduced in this PR.
- [x] I've made sure all auto checks have passed.
---------
Signed-off-by: Abhijeetsingh Meena <abhijeet040403@gmail.com>
Co-authored-by: Eric Zhu <ekzhu@users.noreply.github.com>
<!-- Thank you for your contribution! Please review
https://microsoft.github.io/autogen/docs/Contribute before opening a
pull request. -->
<!-- Please add a reviewer to the assignee section when you create a PR.
If you don't have the access to it, we will shortly find a reviewer and
assign them to your PR. -->
## Why are these changes needed?
<!-- Please give a short summary of the change and the problem this
solves. -->
## Related issue number
<!-- For example: "Closes #1234" -->
## Checks
- [x] I've included any doc changes needed for
<https://microsoft.github.io/autogen/>. See
<https://github.com/microsoft/autogen/blob/main/CONTRIBUTING.md> to
build and test documentation locally.
- [x] I've added tests (if relevant) corresponding to the changes
introduced in this PR.
- [x] I've made sure all auto checks have passed.
Signed-off-by: zhanluxianshen <zhanluxianshen@163.com>
Please refer to #6123 for full context.
That issue outlines several design and behavioral problems with
`SocietyOfMindAgent`.
This DRAFT PR focuses on resolving the most critical and broken
behaviors first.
Here is the error list
🔍 SocietyOfMindAgent: Design Issues and Historical Comparison (v0.2 vs
v0.4+)
### ✅ P1–P4 Regression Issue Table (Updated with Fixes in PR #6142)
| ID | Description | Current v0.4+ Issue | Resolution in PR #6142 | Was
it a problem in v0.2? | Notes |
|-----|-------------|----------------------|--------------------------|----------------------------|-------|
| **P1** | `inner_messages` leaks into outer team termination evaluation
| `Response.inner_messages` is appended to the outer team's
`_message_thread`, affecting termination conditions. Violates
encapsulation. | ✅ `inner_messages` is excluded from `_message_thread`,
avoiding contamination of outer termination logic. | ❌ No | Structural
boundary is now enforced |
| **P2** | Inner team does not execute when outer message history is
empty | In chained executions, if no new outer message exists, no task
is created and the inner team is skipped entirely | ✅ Detects absence of
new outer message and reuses the previous task, passing it via a handoff
message. This ensures the inner team always receives a valid task to
execute | ❌ No | The issue was silent task omission, not summary
failure. Summary succeeds as a downstream effect |
| **P3** | Summary LLM prompt is built from external input only | Prompt
is constructed using external message history, ignoring internal
reasoning | ✅ Prompt construction now uses
`final_response.inner_messages`, restoring internal reasoning as the
source of summarization | ❌ No | Matches v0.2 internal monologue
behavior |
| **P4** | External input is included in summary prompt (possibly
incorrectly) | Outer messages are used in the final LLM summarization
prompt | ✅ Resolved via the same fix as P3; outer messages are no longer
used for summary | ❌ No | Redundant with P3, now fully addressed |
<!-- Thank you for your contribution! Please review
https://microsoft.github.io/autogen/docs/Contribute before opening a
pull request. -->
<!-- Please add a reviewer to the assignee section when you create a PR.
If you don't have the access to it, we will shortly find a reviewer and
assign them to your PR. -->
## Why are these changes needed?
<!-- Please give a short summary of the change and the problem this
solves. -->
## Related issue number
resolve#6123
Blocked #6168 (Sometimes SoMA send last whitespace message)
related #6187
<!-- For example: "Closes #1234" -->
## Checks
- [ ] I've included any doc changes needed for
<https://microsoft.github.io/autogen/>. See
<https://github.com/microsoft/autogen/blob/main/CONTRIBUTING.md> to
build and test documentation locally.
- [ ] I've added tests (if relevant) corresponding to the changes
introduced in this PR.
- [ ] I've made sure all auto checks have passed.
---------
Co-authored-by: Eric Zhu <ekzhu@users.noreply.github.com>