Repository: microsoft/autogen
Pull request: Add support for specifying the languages to parse from the `CodeExecutorAgent` response
## 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.


Problem statement:
CodeExecutorAgent should be able to ignore certain languages
### What happened?

**Describe the bug**
code executor throw Unsupported language: echarts
**To Reproduce**
```
code_executor_agent = CodeExecutorAgent(
            name="code_executor", code_executor=code_executor
        )

        assistant_agent = AssistantAgent(
            name="coder_agent",
            model_client=self.model_client,
            system_message=DEFAULT_SYSTEM_MESSAGE,
            model_client_stream=True,
        )

        # The termination condition is a combination of text termination and max message termination, either of which will cause the chat to terminate.
        termination = TextMentionTermination("TERMINATE") | MaxMessageTermination(20)

        # The group chat will alternate between the assistant and the code executor.
        self.group_chat = RoundRobinGroupChat(
            [assistant_agent, code_executor_agent], termination_condition=termination
        )
```

When the model generates an ECharts code block in Markdown format (```echarts), the following exception is thrown:
```
    |     async for message in self.group_chat.run_stream(
    |   File "d:\Develop\conda\envs\ai_table\Lib\site-packages\autogen_agentchat\teams\_group_chat\_base_group_chat.py", line 518, in run_stream
    |     raise RuntimeError(str(message.error))
    | RuntimeError: ValueError: Unsupported language: echarts
    | Traceback:
    | Traceback (most recent call last):
    |
    |   File "d:\Develop\conda\envs\ai_table\Lib\site-packages\autogen_agentchat\teams\_group_chat\_chat_agent_container.py", line 79, in handle_request
    |     async for msg in self._agent.on_messages_stream(self._message_buffer, ctx.cancellation_token):
    |
    |   File "d:\Develop\conda\envs\ai_table\Lib\site-packages\autogen_agentchat\agents\_code_executor_agent.py", line 427, in on_messages_stream
    |     execution_result = await self.execute_code_block(code_blocks, cancellation_token)
    |                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |
    |   File "d:\Develop\conda\envs\ai_table\Lib\site-packages\autogen_agentchat\agents\_code_executor_agent.py", line 576, in execute_code_block
    |     result = await self._code_executor.execute_code_blocks(code_blocks, cancellation_token=cancellation_token)
    |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |
    |   File "d:\Develop\conda\envs\ai_table\Lib\site-packages\autogen_ext\code_executors\docker\_docker_code_executor.py", line 403, in execute_code_blocks
    |     return await self._execute_code_dont_check_setup(code_blocks, cancellation_token)
    |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |
    |   File "d:\Develop\conda\envs\ai_table\Lib\site-packages\autogen_ext\code_executors\docker\_docker_code_executor.py", line 344, in _execute_code_dont_check_setup
    |     command = ["timeout", str(self._timeout), lang_to_cmd(lang), filename]
    |                                               ^^^^^^^^^^^^^^^^^
    |
    |   File "d:\Develop\conda\envs\ai_table\Lib\site-packages\autogen_ext\code_executors\_common.py", line 171, in lang_to_cmd
    |     raise ValueError(f"Unsupported language: {lang}")
    |
    | ValueError: Unsupported language: echarts

```

**Expected behavior**
Unsupported code should not be executed.


### Which packages was the bug in?

Python AgentChat (autogen-agentchat>=0.4.0)

### AutoGen library version.

Python 0.5.6

### Other library version.

_No response_

### Model used

_No response_

### Model provider

Azure OpenAI

### Other model provider

_No response_

### Python version

3.10

### .NET version

None

### Operating system

Windows

Hints from issue discussion:
What is your scenario here? Are there other code blocks you need to execute that are generated in addition to the echarts one?
> What is your scenario here? Are there other code blocks you need to execute that are generated in addition to the echarts one?

There are also Python and Shell code blocks, but the ECharts code block is used for frontend display. The code executor should not execute the ECharts code block.
Can the code executor skip unsupported code blocks instead of throwing errors?
Thanks for the info. I think the required fix here is that the [`CodeExecutorAgent`](https://microsoft.github.io/autogen/stable//reference/python/autogen_agentchat.agents.html#autogen_agentchat.agents.CodeExecutorAgent) should be able to specify either explicit languages to include or exclude.

Are you interested in contributing this change?

Commit message: Add support for specifying the languages to parse from the agent's response (#6471)

Signed-off-by: Abhijeetsingh Meena <abhijeet040403@gmail.com>

Patch to review:
diff --git a/python/packages/autogen-agentchat/src/autogen_agentchat/agents/_code_executor_agent.py b/python/packages/autogen-agentchat/src/autogen_agentchat/agents/_code_executor_agent.py
index 2efa6d5a7400..9428157113f5 100644
--- a/python/packages/autogen-agentchat/src/autogen_agentchat/agents/_code_executor_agent.py
+++ b/python/packages/autogen-agentchat/src/autogen_agentchat/agents/_code_executor_agent.py
@@ -54,6 +54,7 @@ class CodeExecutorAgentConfig(BaseModel):
     system_message: str | None = None
     model_client_stream: bool = False
     model_context: ComponentModel | None = None
+    supported_languages: List[str] | None = None
 
 
 class RetryDecision(BaseModel):
@@ -110,6 +111,8 @@ class CodeExecutorAgent(BaseChatAgent, Component[CodeExecutorAgentConfig]):
             This is only used if `model_client` is not provided.
         max_retries_on_error (int, optional): The maximum number of retries on error. If the code execution fails, the agent will retry up to this number of times.
             If the code execution fails after this number of retries, the agent will yield a reflection result.
+        supported_languages (list[str], optional): List of programming languages that will be parsed and executed from agent response;
+            others will be ignored. Defaults to ["python", "bash"].
 
 
     .. note::
@@ -345,6 +348,7 @@ def __init__(
         description: str | None = None,
         system_message: str | None = DEFAULT_SYSTEM_MESSAGE,
         sources: Sequence[str] | None = None,
+        supported_languages: List[str] | None = None,
     ) -> None:
         if description is None:
             if model_client is None:
@@ -358,6 +362,13 @@ def __init__(
         self._model_client_stream = model_client_stream
         self._max_retries_on_error = max_retries_on_error
 
+        if supported_languages:
+            self._supported_languages = supported_languages
+        else:
+            self._supported_languages = ["python", "bash"]
+
+        self._supported_languages_regex = "|".join(re.escape(lang) for lang in self._supported_languages)
+
         self._model_client = None
         if model_client is not None:
             self._model_client = model_client
@@ -589,7 +600,7 @@ async def on_reset(self, cancellation_token: CancellationToken) -> None:
         pass
 
     def _extract_markdown_code_blocks(self, markdown_text: str) -> List[CodeBlock]:
-        pattern = re.compile(r"```(?:\s*([\w\+\-]+))?\n([\s\S]*?)```")
+        pattern = re.compile(rf"```(?:\s*({self._supported_languages_regex}))\n([\s\S]*?)```")
         matches = pattern.findall(markdown_text)
         code_blocks: List[CodeBlock] = []
         for match in matches:
@@ -612,6 +623,7 @@ def _to_config(self) -> CodeExecutorAgentConfig:
             ),
             model_client_stream=self._model_client_stream,
             model_context=self._model_context.dump_component(),
+            supported_languages=self._supported_languages,
         )
 
     @classmethod
@@ -627,6 +639,7 @@ def _from_config(cls, config: CodeExecutorAgentConfig) -> Self:
             system_message=config.system_message,
             model_client_stream=config.model_client_stream,
             model_context=ChatCompletionContext.load_component(config.model_context) if config.model_context else None,
+            supported_languages=config.supported_languages,
         )
 
     @staticmethod


Code review comments:
diff_hunk: @@ -358,6 +362,13 @@ def __init__(
         self._model_client_stream = model_client_stream
         self._max_retries_on_error = max_retries_on_error
 
+        if supported_languages:
+            self._supported_languages = supported_languages
+        else:
+            self._supported_languages = ["python", "bash"]
path: python/packages/autogen-agentchat/src/autogen_agentchat/agents/_code_executor_agent.py
text: @user1:
[nitpick] The default list is hardcoded in both the docstring and here—consider defining a single constant for defaults to avoid duplication.
```suggestion
            self._supported_languages = CodeExecutorAgent.DEFAULT_SUPPORTED_LANGUAGES
```

diff_hunk: @@ -358,6 +362,13 @@ def __init__(
         self._model_client_stream = model_client_stream
         self._max_retries_on_error = max_retries_on_error
 
+        if supported_languages:
path: python/packages/autogen-agentchat/src/autogen_agentchat/agents/_code_executor_agent.py
text: @user1:
Using a truthiness check treats an empty list as false and falls back to defaults. Change the condition to `if supported_languages is not None:` to honor explicitly passed empty lists.
```suggestion
        if supported_languages is not None:
```

diff_hunk: @@ -110,6 +111,8 @@ class CodeExecutorAgent(BaseChatAgent, Component[CodeExecutorAgentConfig]):
             This is only used if `model_client` is not provided.
         max_retries_on_error (int, optional): The maximum number of retries on error. If the code execution fails, the agent will retry up to this number of times.
             If the code execution fails after this number of retries, the agent will yield a reflection result.
+        supported_languages (list[str], optional): List of programming languages that will be parsed and executed from agent response;
path: python/packages/autogen-agentchat/src/autogen_agentchat/agents/_code_executor_agent.py
text: @user1:
The docstring uses `list[str]` but the type annotation is `List[str]`. Align these to use the same notation for consistency.
```suggestion
        supported_languages (List[str], optional): List of programming languages that will be parsed and executed from agent response;
```