Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RichConsole: Prettify m1 CLI console using rich #4806 #5123

Merged
merged 7 commits into from
Jan 24, 2025
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
gziz marked this conversation as resolved.
Show resolved Hide resolved
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
Loading