Skip to content

Commit

Permalink
RichConsole: prettify m1 CLI console #4806
Browse files Browse the repository at this point in the history
  • Loading branch information
gziz committed Jan 21, 2025
1 parent 34bc82e commit 15a758d
Show file tree
Hide file tree
Showing 3 changed files with 224 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@
"""

from ._console import Console, UserInputManager
from ._rich_console import RichConsole

__all__ = ["Console", "UserInputManager"]
__all__ = ["Console", "RichConsole", "UserInputManager"]
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
import asyncio
import os
import sys
import time
from typing import (
AsyncGenerator,
Awaitable,
List,
Optional,
Tuple,
TypeVar,
cast,
)

from autogen_core import Image
from autogen_core.models import RequestUsage
from rich.align import AlignMethod
from rich.console import Console
from rich.panel import Panel

from autogen_agentchat.base import Response, TaskResult
from autogen_agentchat.messages import (
AgentEvent,
ChatMessage,
MultiModalMessage,
UserInputRequestedEvent,
)
from autogen_agentchat.ui._console import UserInputManager

AGENT_COLORS = {
"user": "bright_green",
"MagenticOneOrchestrator": "bright_blue",
"WebSurfer": "bright_yellow",
"FileSurfer": "bright_cyan",
"Coder": "bright_magenta",
"Executor": "bright_red",
}
DEFAULT_AGENT_COLOR = "white"

AGENT_ALIGNMENTS: dict[str, AlignMethod] = {"user": "right", "MagenticOneOrchestrator": "center"}
DEFAULT_AGENT_ALIGNMENT: AlignMethod = "left"


def _is_running_in_iterm() -> bool:
return os.getenv("TERM_PROGRAM") == "iTerm.app"

Check warning on line 45 in python/packages/autogen-agentchat/src/autogen_agentchat/ui/_rich_console.py

View check run for this annotation

Codecov / codecov/patch

python/packages/autogen-agentchat/src/autogen_agentchat/ui/_rich_console.py#L45

Added line #L45 was not covered by tests


def _is_output_a_tty() -> bool:
return sys.stdout.isatty()

Check warning on line 49 in python/packages/autogen-agentchat/src/autogen_agentchat/ui/_rich_console.py

View check run for this annotation

Codecov / codecov/patch

python/packages/autogen-agentchat/src/autogen_agentchat/ui/_rich_console.py#L49

Added line #L49 was not covered by tests


T = TypeVar("T", bound=TaskResult | Response)


def aprint(output: str, end: str = "\n") -> Awaitable[None]:
return asyncio.to_thread(print, output, end=end)

Check warning on line 56 in python/packages/autogen-agentchat/src/autogen_agentchat/ui/_rich_console.py

View check run for this annotation

Codecov / codecov/patch

python/packages/autogen-agentchat/src/autogen_agentchat/ui/_rich_console.py#L56

Added line #L56 was not covered by tests


def _extract_message_content(message: AgentEvent | ChatMessage) -> Tuple[List[str], List[Image]]:
if isinstance(message, MultiModalMessage):
text_parts = [item for item in message.content if isinstance(item, str)]
image_parts = [item for item in message.content if isinstance(item, Image)]

Check warning on line 62 in python/packages/autogen-agentchat/src/autogen_agentchat/ui/_rich_console.py

View check run for this annotation

Codecov / codecov/patch

python/packages/autogen-agentchat/src/autogen_agentchat/ui/_rich_console.py#L60-L62

Added lines #L60 - L62 were not covered by tests
else:
text_parts = [str(message.content)]
image_parts = []
return text_parts, image_parts

Check warning on line 66 in python/packages/autogen-agentchat/src/autogen_agentchat/ui/_rich_console.py

View check run for this annotation

Codecov / codecov/patch

python/packages/autogen-agentchat/src/autogen_agentchat/ui/_rich_console.py#L64-L66

Added lines #L64 - L66 were not covered by tests


async def _aprint_panel(console: Console, text: str, title: str) -> None:
color = AGENT_COLORS.get(title, DEFAULT_AGENT_COLOR)
title_align = AGENT_ALIGNMENTS.get(title, DEFAULT_AGENT_ALIGNMENT)

Check warning on line 71 in python/packages/autogen-agentchat/src/autogen_agentchat/ui/_rich_console.py

View check run for this annotation

Codecov / codecov/patch

python/packages/autogen-agentchat/src/autogen_agentchat/ui/_rich_console.py#L70-L71

Added lines #L70 - L71 were not covered by tests

await asyncio.to_thread(

Check warning on line 73 in python/packages/autogen-agentchat/src/autogen_agentchat/ui/_rich_console.py

View check run for this annotation

Codecov / codecov/patch

python/packages/autogen-agentchat/src/autogen_agentchat/ui/_rich_console.py#L73

Added line #L73 was not covered by tests
console.print,
Panel(
text,
title=title,
title_align=title_align,
border_style=color,
),
)


async def _aprint_message_content(
console: Console,
text_parts: List[str],
image_parts: List[Image],
source: str,
*,
render_image_iterm: bool = False,
) -> None:
if text_parts:
await _aprint_panel(console, "\n".join(text_parts), source)

Check warning on line 93 in python/packages/autogen-agentchat/src/autogen_agentchat/ui/_rich_console.py

View check run for this annotation

Codecov / codecov/patch

python/packages/autogen-agentchat/src/autogen_agentchat/ui/_rich_console.py#L92-L93

Added lines #L92 - L93 were not covered by tests

for img in image_parts:
if render_image_iterm:
await aprint(_image_to_iterm(img))

Check warning on line 97 in python/packages/autogen-agentchat/src/autogen_agentchat/ui/_rich_console.py

View check run for this annotation

Codecov / codecov/patch

python/packages/autogen-agentchat/src/autogen_agentchat/ui/_rich_console.py#L95-L97

Added lines #L95 - L97 were not covered by tests
else:
await aprint("<image>\n")

Check warning on line 99 in python/packages/autogen-agentchat/src/autogen_agentchat/ui/_rich_console.py

View check run for this annotation

Codecov / codecov/patch

python/packages/autogen-agentchat/src/autogen_agentchat/ui/_rich_console.py#L99

Added line #L99 was not covered by tests


async def RichConsole(
stream: AsyncGenerator[AgentEvent | ChatMessage | T, None],
*,
no_inline_images: bool = False,
output_stats: bool = False,
user_input_manager: UserInputManager | None = None,
) -> T:
"""
Consumes the message stream from :meth:`~autogen_agentchat.base.TaskRunner.run_stream`
or :meth:`~autogen_agentchat.base.ChatAgent.on_messages_stream` and renders the messages to the console.
Returns the last processed TaskResult or Response.
.. note::
`output_stats` is experimental and the stats may not be accurate.
It will be improved in future releases.
Args:
stream (AsyncGenerator[AgentEvent | ChatMessage | TaskResult, None] | AsyncGenerator[AgentEvent | ChatMessage | Response, None]): Message stream to render.
This can be from :meth:`~autogen_agentchat.base.TaskRunner.run_stream` or :meth:`~autogen_agentchat.base.ChatAgent.on_messages_stream`.
no_inline_images (bool, optional): If terminal is iTerm2 will render images inline. Use this to disable this behavior. Defaults to False.
output_stats (bool, optional): (Experimental) If True, will output a summary of the messages and inline token usage info. Defaults to False.
Returns:
last_processed: A :class:`~autogen_agentchat.base.TaskResult` if the stream is from :meth:`~autogen_agentchat.base.TaskRunner.run_stream`
or a :class:`~autogen_agentchat.base.Response` if the stream is from :meth:`~autogen_agentchat.base.ChatAgent.on_messages_stream`.
"""
render_image_iterm = _is_running_in_iterm() and _is_output_a_tty() and not no_inline_images
start_time = time.time()
total_usage = RequestUsage(prompt_tokens=0, completion_tokens=0)
rich_console = Console()

Check warning on line 132 in python/packages/autogen-agentchat/src/autogen_agentchat/ui/_rich_console.py

View check run for this annotation

Codecov / codecov/patch

python/packages/autogen-agentchat/src/autogen_agentchat/ui/_rich_console.py#L129-L132

Added lines #L129 - L132 were not covered by tests

last_processed: Optional[T] = None

Check warning on line 134 in python/packages/autogen-agentchat/src/autogen_agentchat/ui/_rich_console.py

View check run for this annotation

Codecov / codecov/patch

python/packages/autogen-agentchat/src/autogen_agentchat/ui/_rich_console.py#L134

Added line #L134 was not covered by tests

async for message in stream:
if isinstance(message, TaskResult):
duration = time.time() - start_time
if output_stats:
output = (

Check warning on line 140 in python/packages/autogen-agentchat/src/autogen_agentchat/ui/_rich_console.py

View check run for this annotation

Codecov / codecov/patch

python/packages/autogen-agentchat/src/autogen_agentchat/ui/_rich_console.py#L136-L140

Added lines #L136 - L140 were not covered by tests
f"Number of messages: {len(message.messages)}\n"
f"Finish reason: {message.stop_reason}\n"
f"Total prompt tokens: {total_usage.prompt_tokens}\n"
f"Total completion tokens: {total_usage.completion_tokens}\n"
f"Duration: {duration:.2f} seconds\n"
)
await _aprint_panel(rich_console, output, "Summary")

Check warning on line 147 in python/packages/autogen-agentchat/src/autogen_agentchat/ui/_rich_console.py

View check run for this annotation

Codecov / codecov/patch

python/packages/autogen-agentchat/src/autogen_agentchat/ui/_rich_console.py#L147

Added line #L147 was not covered by tests

last_processed = message # type: ignore

Check warning on line 149 in python/packages/autogen-agentchat/src/autogen_agentchat/ui/_rich_console.py

View check run for this annotation

Codecov / codecov/patch

python/packages/autogen-agentchat/src/autogen_agentchat/ui/_rich_console.py#L149

Added line #L149 was not covered by tests

elif isinstance(message, Response):
duration = time.time() - start_time

Check warning on line 152 in python/packages/autogen-agentchat/src/autogen_agentchat/ui/_rich_console.py

View check run for this annotation

Codecov / codecov/patch

python/packages/autogen-agentchat/src/autogen_agentchat/ui/_rich_console.py#L151-L152

Added lines #L151 - L152 were not covered by tests

# Print final response.
text_parts, image_parts = _extract_message_content(message.chat_message)
if message.chat_message.models_usage:
if output_stats:
text_parts.append(

Check warning on line 158 in python/packages/autogen-agentchat/src/autogen_agentchat/ui/_rich_console.py

View check run for this annotation

Codecov / codecov/patch

python/packages/autogen-agentchat/src/autogen_agentchat/ui/_rich_console.py#L155-L158

Added lines #L155 - L158 were not covered by tests
f"[Prompt tokens: {message.chat_message.models_usage.prompt_tokens}, Completion tokens: {message.chat_message.models_usage.completion_tokens}]"
)
total_usage.completion_tokens += message.chat_message.models_usage.completion_tokens
total_usage.prompt_tokens += message.chat_message.models_usage.prompt_tokens

Check warning on line 162 in python/packages/autogen-agentchat/src/autogen_agentchat/ui/_rich_console.py

View check run for this annotation

Codecov / codecov/patch

python/packages/autogen-agentchat/src/autogen_agentchat/ui/_rich_console.py#L161-L162

Added lines #L161 - L162 were not covered by tests

await _aprint_message_content(

Check warning on line 164 in python/packages/autogen-agentchat/src/autogen_agentchat/ui/_rich_console.py

View check run for this annotation

Codecov / codecov/patch

python/packages/autogen-agentchat/src/autogen_agentchat/ui/_rich_console.py#L164

Added line #L164 was not covered by tests
rich_console,
text_parts,
image_parts,
message.chat_message.source,
render_image_iterm=render_image_iterm,
)

# Print summary.
if output_stats:
num_inner_messages = len(message.inner_messages) if message.inner_messages is not None else 0
output = (

Check warning on line 175 in python/packages/autogen-agentchat/src/autogen_agentchat/ui/_rich_console.py

View check run for this annotation

Codecov / codecov/patch

python/packages/autogen-agentchat/src/autogen_agentchat/ui/_rich_console.py#L173-L175

Added lines #L173 - L175 were not covered by tests
f"Number of inner messages: {num_inner_messages}\n"
f"Total prompt tokens: {total_usage.prompt_tokens}\n"
f"Total completion tokens: {total_usage.completion_tokens}\n"
f"Duration: {duration:.2f} seconds\n"
)
await _aprint_panel(rich_console, output, "Summary")

Check warning on line 181 in python/packages/autogen-agentchat/src/autogen_agentchat/ui/_rich_console.py

View check run for this annotation

Codecov / codecov/patch

python/packages/autogen-agentchat/src/autogen_agentchat/ui/_rich_console.py#L181

Added line #L181 was not covered by tests

# mypy ignore
last_processed = message # type: ignore

Check warning on line 184 in python/packages/autogen-agentchat/src/autogen_agentchat/ui/_rich_console.py

View check run for this annotation

Codecov / codecov/patch

python/packages/autogen-agentchat/src/autogen_agentchat/ui/_rich_console.py#L184

Added line #L184 was not covered by tests
# We don't want to print UserInputRequestedEvent messages, we just use them to signal the user input event.
elif isinstance(message, UserInputRequestedEvent):
if user_input_manager is not None:
user_input_manager.notify_event_received(message.request_id)

Check warning on line 188 in python/packages/autogen-agentchat/src/autogen_agentchat/ui/_rich_console.py

View check run for this annotation

Codecov / codecov/patch

python/packages/autogen-agentchat/src/autogen_agentchat/ui/_rich_console.py#L186-L188

Added lines #L186 - L188 were not covered by tests
else:
# Cast required for mypy to be happy
message = cast(AgentEvent | ChatMessage, message) # type: ignore

Check warning on line 191 in python/packages/autogen-agentchat/src/autogen_agentchat/ui/_rich_console.py

View check run for this annotation

Codecov / codecov/patch

python/packages/autogen-agentchat/src/autogen_agentchat/ui/_rich_console.py#L191

Added line #L191 was not covered by tests

text_parts, image_parts = _extract_message_content(message)

Check warning on line 193 in python/packages/autogen-agentchat/src/autogen_agentchat/ui/_rich_console.py

View check run for this annotation

Codecov / codecov/patch

python/packages/autogen-agentchat/src/autogen_agentchat/ui/_rich_console.py#L193

Added line #L193 was not covered by tests
# Add usage stats if needed
if message.models_usage:
if output_stats:
text_parts.append(

Check warning on line 197 in python/packages/autogen-agentchat/src/autogen_agentchat/ui/_rich_console.py

View check run for this annotation

Codecov / codecov/patch

python/packages/autogen-agentchat/src/autogen_agentchat/ui/_rich_console.py#L195-L197

Added lines #L195 - L197 were not covered by tests
f"[Prompt tokens: {message.models_usage.prompt_tokens}, Completion tokens: {message.models_usage.completion_tokens}]"
)
total_usage.completion_tokens += message.models_usage.completion_tokens
total_usage.prompt_tokens += message.models_usage.prompt_tokens

Check warning on line 201 in python/packages/autogen-agentchat/src/autogen_agentchat/ui/_rich_console.py

View check run for this annotation

Codecov / codecov/patch

python/packages/autogen-agentchat/src/autogen_agentchat/ui/_rich_console.py#L200-L201

Added lines #L200 - L201 were not covered by tests

await _aprint_message_content(

Check warning on line 203 in python/packages/autogen-agentchat/src/autogen_agentchat/ui/_rich_console.py

View check run for this annotation

Codecov / codecov/patch

python/packages/autogen-agentchat/src/autogen_agentchat/ui/_rich_console.py#L203

Added line #L203 was not covered by tests
rich_console,
text_parts,
image_parts,
message.source,
render_image_iterm=render_image_iterm,
)

if last_processed is None:
raise ValueError("No TaskResult or Response was processed.")

Check warning on line 212 in python/packages/autogen-agentchat/src/autogen_agentchat/ui/_rich_console.py

View check run for this annotation

Codecov / codecov/patch

python/packages/autogen-agentchat/src/autogen_agentchat/ui/_rich_console.py#L211-L212

Added lines #L211 - L212 were not covered by tests

return last_processed

Check warning on line 214 in python/packages/autogen-agentchat/src/autogen_agentchat/ui/_rich_console.py

View check run for this annotation

Codecov / codecov/patch

python/packages/autogen-agentchat/src/autogen_agentchat/ui/_rich_console.py#L214

Added line #L214 was not covered by tests


# iTerm2 image rendering protocol: https://iterm2.com/documentation-images.html
def _image_to_iterm(image: Image) -> str:
image_data = image.to_base64()
return f"\033]1337;File=inline=1:{image_data}\a\n"

Check warning on line 220 in python/packages/autogen-agentchat/src/autogen_agentchat/ui/_rich_console.py

View check run for this annotation

Codecov / codecov/patch

python/packages/autogen-agentchat/src/autogen_agentchat/ui/_rich_console.py#L219-L220

Added lines #L219 - L220 were not covered by tests
4 changes: 2 additions & 2 deletions python/packages/magentic-one-cli/src/magentic_one_cli/_m1.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import warnings
from typing import Optional

from autogen_agentchat.ui import Console, UserInputManager
from autogen_agentchat.ui import RichConsole, UserInputManager
from autogen_core import CancellationToken
from autogen_ext.models.openai import OpenAIChatCompletionClient
from autogen_ext.teams.magentic_one import MagenticOne
Expand Down Expand Up @@ -49,7 +49,7 @@ async def run_task(task: str, hil_mode: bool) -> None:
input_manager = UserInputManager(callback=cancellable_input)
client = OpenAIChatCompletionClient(model="gpt-4o")
m1 = MagenticOne(client=client, hil_mode=hil_mode, input_func=input_manager.get_wrapped_callback())
await Console(m1.run_stream(task=task), output_stats=False, user_input_manager=input_manager)
await RichConsole(m1.run_stream(task=task), output_stats=False, user_input_manager=input_manager)

task = args.task[0]
asyncio.run(run_task(task, not args.no_hil))
Expand Down

0 comments on commit 15a758d

Please sign in to comment.