diff --git a/src/crewai/llm.py b/src/crewai/llm.py index 440cbf903..89ddfe2a1 100644 --- a/src/crewai/llm.py +++ b/src/crewai/llm.py @@ -67,33 +67,72 @@ class FilteredStream(io.TextIOBase): self._lock = threading.Lock() with self._lock: - # Filter out extraneous messages from LiteLLM + lower_s = s.lower() + + # Skip common noisy LiteLLM banners and any other lines that contain "litellm" if ( - "Give Feedback / Get Help: https://github.com/BerriAI/litellm/issues/new" - in s - or "LiteLLM.Info: If you need to debug this error, use `litellm._turn_on_debug()`" - in s + "give feedback / get help" in lower_s + or "litellm.info:" in lower_s + or "litellm" in lower_s + or "Consider using a smaller input or implementing a text splitting strategy" in lower_s ): return 0 + return self._original_stream.write(s) def flush(self): with self._lock: return self._original_stream.flush() + def __getattr__(self, name): + """Delegate attribute access to the wrapped original stream. + + This ensures compatibility with libraries (e.g., Rich) that rely on + attributes such as `encoding`, `isatty`, `buffer`, etc., which may not + be explicitly defined on this proxy class. + """ + return getattr(self._original_stream, name) + + # Delegate common properties/methods explicitly so they aren't shadowed by + # the TextIOBase defaults (e.g., .encoding returns None by default, which + # confuses Rich). These explicit pass-throughs ensure the wrapped Console + # still sees a fully-featured stream. + @property + def encoding(self): + return getattr(self._original_stream, "encoding", "utf-8") + + def isatty(self): + return self._original_stream.isatty() + + def fileno(self): + return self._original_stream.fileno() + + def writable(self): + return True + + +# Apply the filtered stream globally so that any subsequent writes containing the filtered +# keywords (e.g., "litellm") are hidden from terminal output. We guard against double +# wrapping to ensure idempotency in environments where this module might be reloaded. +if not isinstance(sys.stdout, FilteredStream): + sys.stdout = FilteredStream(sys.stdout) +if not isinstance(sys.stderr, FilteredStream): + sys.stderr = FilteredStream(sys.stderr) + LLM_CONTEXT_WINDOW_SIZES = { # openai "gpt-4": 8192, "gpt-4o": 128000, - "gpt-4o-mini": 128000, + "gpt-4o-mini": 200000, "gpt-4-turbo": 128000, "gpt-4.1": 1047576, # Based on official docs "gpt-4.1-mini-2025-04-14": 1047576, "gpt-4.1-nano-2025-04-14": 1047576, "o1-preview": 128000, "o1-mini": 128000, - "o3-mini": 200000, # Based on official o3-mini specifications + "o3-mini": 200000, + "o4-mini": 200000, # gemini "gemini-2.0-flash": 1048576, "gemini-2.0-flash-thinking-exp-01-21": 32768, @@ -208,7 +247,7 @@ LLM_CONTEXT_WINDOW_SIZES = { } DEFAULT_CONTEXT_WINDOW_SIZE = 8192 -CONTEXT_WINDOW_USAGE_RATIO = 0.75 +CONTEXT_WINDOW_USAGE_RATIO = 0.85 @contextmanager @@ -219,12 +258,7 @@ def suppress_warnings(): "ignore", message="open_text is deprecated*", category=DeprecationWarning ) - # Redirect stdout and stderr - with ( - redirect_stdout(FilteredStream(sys.stdout)), - redirect_stderr(FilteredStream(sys.stderr)), - ): - yield + yield class Delta(TypedDict): diff --git a/src/crewai/utilities/agent_utils.py b/src/crewai/utilities/agent_utils.py index e580c26ce..4de69496a 100644 --- a/src/crewai/utilities/agent_utils.py +++ b/src/crewai/utilities/agent_utils.py @@ -44,7 +44,7 @@ def render_text_description_and_args( tools: Sequence[Union[CrewStructuredTool, BaseTool]], ) -> str: """Render the tool name, description, and args in plain text. - + search: This tool is used for search, args: {"query": {"type": "string"}} calculator: This tool is used for math, \ args: {"expression": {"type": "string"}} @@ -309,7 +309,7 @@ def handle_context_length( """ if respect_context_window: printer.print( - content="Context length exceeded. Summarizing content to fit the model context window.", + content="Context length exceeded. Summarizing content to fit the model context window. Might take a while...", color="yellow", ) summarize_messages(messages, llm, callbacks, i18n) @@ -337,15 +337,22 @@ def summarize_messages( callbacks: List of callbacks for LLM i18n: I18N instance for messages """ + messages_string = " ".join([message["content"] for message in messages]) messages_groups = [] - for message in messages: - content = message["content"] - cut_size = llm.get_context_window_size() - for i in range(0, len(content), cut_size): - messages_groups.append({"content": content[i : i + cut_size]}) + + cut_size = llm.get_context_window_size() + + for i in range(0, len(messages_string), cut_size): + messages_groups.append({"content": messages_string[i : i + cut_size]}) summarized_contents = [] - for group in messages_groups: + + total_groups = len(messages_groups) + for idx, group in enumerate(messages_groups, 1): + Printer().print( + content=f"Summarizing {idx}/{total_groups}...", + color="yellow", + ) summary = llm.call( [ format_message_for_llm(