Skip to content

fix: YoutubeChannelSearchTool passes bare handle instead of full URL (#5429)#5829

Open
NIK-TIGER-BILL wants to merge 3 commits into
crewAIInc:mainfrom
NIK-TIGER-BILL:fix-youtube-channel-search-5429
Open

fix: YoutubeChannelSearchTool passes bare handle instead of full URL (#5429)#5829
NIK-TIGER-BILL wants to merge 3 commits into
crewAIInc:mainfrom
NIK-TIGER-BILL:fix-youtube-channel-search-5429

Conversation

@NIK-TIGER-BILL
Copy link
Copy Markdown
Contributor

@NIK-TIGER-BILL NIK-TIGER-BILL commented May 15, 2026

Purpose

YoutubeChannelSearchTool.add() forwarded a bare channel handle (e.g. @krishnaik06) unchanged to the RagTool adapter. The downstream YoutubeChannelLoader then raised ValueError: Invalid YouTube channel URL because it expects a fully-qualified https://www.youtube.com/... URL.

Convert the handle to a full YouTube URL before passing it to the adapter.

Closes #5429

Test Plan

Added lib/crewai-tools/tests/tools/test_youtube_channel_search_tool.py with two unit tests that assert the adapter receives a proper https://www.youtube.com/@handle URL.

Test Result

Before fix: ValueError: Invalid YouTube channel URL: @krishnaik06
After fix: adapter receives https://www.youtube.com/@krishnaik06

Summary by CodeRabbit

  • New Features

    • YouTube Channel Search Tool now accepts and correctly normalizes channel handles to full YouTube URLs
    • OpenAI completions now include reasoning content as fallback when standard response content is unavailable
  • Tests

    • Added tests for YouTube channel handle URL normalization
    • Added tests for OpenAI reasoning content fallback behavior

Review Change Stack

NIK-TIGER-BILL added 3 commits May 7, 2026 23:19
OpenRouter reasoning models (e.g., Anthropic Sonnet 4.5, Gemini 3.1 Pro)
return a reasoning_content field when the model produces chain-of-thought
output. CrewAI previously only read message.content, which could be None
for these responses, resulting in an empty string being returned.

This change adds a fallback to getattr(message, 'reasoning_content') in both
sync and async non-streaming and streaming chat completion paths, so that
reasoning output is correctly surfaced when content is absent.

Fixes crewAIInc#5537

Signed-off-by: NIK-TIGER-BILL <nik.tiger.bill@github.com>
…content

Replaces getattr(message, 'reasoning_content', '') and getattr(chunk_delta,
'reasoning_content', '') with try/except AttributeError blocks as requested
by reviewer. Also ran ruff check/format.

Signed-off-by: NIK-TIGER-BILL <nik.tiger.bill@github.com>
When YoutubeChannelSearchTool.add() received a bare channel handle
(e.g. '@krishnaik06'), it forwarded the handle unchanged to the
RagTool adapter. The downstream YoutubeChannelLoader then failed
with 'Invalid YouTube channel URL' because it expects a full
https://www.youtube.com/... URL.

Convert the handle to a full YouTube URL before passing it to the
adapter, matching the pattern already used by YoutubeVideoLoader.

Add a unit test that asserts the adapter receives a proper URL.

Fixes: crewAIInc#5429
Signed-off-by: NIK-TIGER-BILL <nik.tiger.bill@github.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 15, 2026

📝 Walkthrough

Walkthrough

This PR addresses two separate features: YouTube channel search tool now normalizes channel handles into full URLs before passing them to downstream loaders, and OpenAI completion handler incorporates reasoning content as fallback text when message content is absent, applied consistently across all sync/async and streaming/non-streaming completion paths.

Changes

YouTube Channel Search Tool URL Normalization

Layer / File(s) Summary
URL conversion and test validation
lib/crewai-tools/src/crewai_tools/tools/youtube_channel_search_tool/youtube_channel_search_tool.py, lib/crewai-tools/tests/tools/test_youtube_channel_search_tool.py
YoutubeChannelSearchTool.add() converts bare channel handles and @-prefixed inputs to full https://www.youtube.com/@{handle} URLs. Tests verify both bare handle conversion and @-prefix preservation with a stub adapter.

OpenAI Reasoning Content Fallback Support

Layer / File(s) Summary
Synchronous completion paths
lib/crewai/src/crewai/llms/providers/openai/completion.py (lines 1681–1689, 1923–1934)
Non-streaming and streaming sync chat completions now extract content from message.content with fallback to message.reasoning_content, using the combined value for validation, stop-word handling, and events.
Asynchronous completion paths
lib/crewai/src/crewai/llms/providers/openai/completion.py (lines 2081–2090, 2234–2245)
Non-streaming and streaming async chat completions apply the same reasoning content fallback logic as sync paths, ensuring consistent output composition.
Test validation
lib/crewai/tests/llms/openai/test_openai.py (lines 16–18, 30–65)
Test imports OpenAI types and validates OpenAICompletion returns reasoning_content when message content is None by mocking the client response.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Poem

A rabbit hops through YouTube's digital halls,
Patching up handles that stumbled and fell,
While reasoning thoughts, when content stalls,
Now echo back loud with a whimsical spell! 🐰✨

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The PR includes changes to openai/completion.py and related tests for handling reasoning_content fallback, which is unrelated to the YouTube fix described in the title and linked issue #5429. Remove the OpenAI reasoning_content changes or move them to a separate PR focused on that feature, as they are outside the scope of fixing the YouTube channel search tool.
Docstring Coverage ⚠️ Warning Docstring coverage is 57.14% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main fix: converting bare YouTube channel handles to full URLs in YoutubeChannelSearchTool, which aligns with the primary code change in the PR.
Linked Issues check ✅ Passed The PR fixes issue #5429 by converting bare channel handles to full URLs before passing to the RagTool adapter, and includes unit tests verifying this behavior.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
lib/crewai-tools/tests/tools/test_youtube_channel_search_tool.py (1)

17-45: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add regression coverage for already-fully-qualified channel URLs.

The suite validates handle normalization, but it misses the full-URL input path from Issue #5429. Add a test asserting tool.add("https://www.youtube.com/@krishnaik06") is forwarded unchanged.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/crewai-tools/tests/tools/test_youtube_channel_search_tool.py` around
lines 17 - 45, Add a regression test that verifies YoutubeChannelSearchTool.add
forwards already-fully-qualified channel URLs unchanged: in the test file add a
new test function (similar to test_add_converts_handle_to_full_url) that creates
a YoutubeChannelSearchTool, assigns a _DummyAdapter to tool.adapter, calls
tool.add("https://www.youtube.com/@krishnaik06") and asserts the adapter
received that exact URL (inspect dummy.calls[0] like the other tests). Ensure
the assertion checks pos_args[0] == "https://www.youtube.com/@krishnaik06" so
the add method does not alter full URLs.
🧹 Nitpick comments (1)
lib/crewai/tests/llms/openai/test_openai.py (1)

30-64: ⚡ Quick win

Add async/streaming parity tests for reasoning fallback.

Nice baseline test, but the changed logic also touches async and streaming paths. Please add tests for: (1) reasoning-only stream fallback, and (2) mixed reasoning+content stream ensuring final output stays content-first.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/crewai/tests/llms/openai/test_openai.py` around lines 30 - 64, Add two
new tests alongside test_openai_completion_reasoning_content_fallback to cover
async/streaming parity: (1) test_openai_completion_reasoning_stream_fallback
should mock the streaming path (patch _get_sync_client or _get_async_client
depending on sync/async implementation) so the chat.completions.create stream
yields chunks whose messages only contain reasoning_content and no content, then
call the LLM in streaming mode and assert the final returned string equals the
reasoning_content; (2)
test_openai_completion_reasoning_mixed_stream_content_first should mock a stream
that first yields chunks with reasoning_content only and later yields a chunk
with content, then call the LLM in streaming mode and assert the final returned
string is the content (content-first wins); use the same helper types from the
file (OpenAICompletion, ChatCompletionMessage, ChatCompletion) and patch the
client methods used in the production code (_get_sync_client/_get_async_client
and chat.completions.create) to simulate the chunked responses.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@lib/crewai-tools/src/crewai_tools/tools/youtube_channel_search_tool/youtube_channel_search_tool.py`:
- Around line 45-50: The code currently always prepends "@" to
youtube_channel_handle then builds channel_url, which corrupts full URLs; update
the logic in the method that sets youtube_channel_handle (the variable used
before calling super().add) to first detect if the input is already a full URL
(e.g., starts with "http://" or "https://" or common YouTube hosts like
"www.youtube.com" / "youtube.com" / "youtu.be") and, if so, use it as
channel_url directly; otherwise normalize bare handles by ensuring they start
with "@" and then build channel_url =
f"https://www.youtube.com/{youtube_channel_handle}" before calling
super().add(..., data_type=DataType.YOUTUBE_CHANNEL).

In `@lib/crewai/src/crewai/llms/providers/openai/completion.py`:
- Around line 1926-1934: The streaming fallback mixes reasoning and final
content because the block using chunk_delta.reasoning_content and
chunk_delta.content appends reasoning into full_response and emits it
immediately; later the actual content may be appended again causing reasoning to
leak. Fix by buffering reasoning separately (e.g., local var reasoning_buffer)
when processing chunks in the streaming path and only append or emit that
buffered reasoning if no content was emitted by the end of the stream; update
the logic around full_response, chunk_delta.content,
chunk_delta.reasoning_content and the _emit_stream_chunk_event calls so emitted
chunks prefer content and only fall back to the buffered reasoning when final
content was never produced.

---

Outside diff comments:
In `@lib/crewai-tools/tests/tools/test_youtube_channel_search_tool.py`:
- Around line 17-45: Add a regression test that verifies
YoutubeChannelSearchTool.add forwards already-fully-qualified channel URLs
unchanged: in the test file add a new test function (similar to
test_add_converts_handle_to_full_url) that creates a YoutubeChannelSearchTool,
assigns a _DummyAdapter to tool.adapter, calls
tool.add("https://www.youtube.com/@krishnaik06") and asserts the adapter
received that exact URL (inspect dummy.calls[0] like the other tests). Ensure
the assertion checks pos_args[0] == "https://www.youtube.com/@krishnaik06" so
the add method does not alter full URLs.

---

Nitpick comments:
In `@lib/crewai/tests/llms/openai/test_openai.py`:
- Around line 30-64: Add two new tests alongside
test_openai_completion_reasoning_content_fallback to cover async/streaming
parity: (1) test_openai_completion_reasoning_stream_fallback should mock the
streaming path (patch _get_sync_client or _get_async_client depending on
sync/async implementation) so the chat.completions.create stream yields chunks
whose messages only contain reasoning_content and no content, then call the LLM
in streaming mode and assert the final returned string equals the
reasoning_content; (2)
test_openai_completion_reasoning_mixed_stream_content_first should mock a stream
that first yields chunks with reasoning_content only and later yields a chunk
with content, then call the LLM in streaming mode and assert the final returned
string is the content (content-first wins); use the same helper types from the
file (OpenAICompletion, ChatCompletionMessage, ChatCompletion) and patch the
client methods used in the production code (_get_sync_client/_get_async_client
and chat.completions.create) to simulate the chunked responses.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: d4bf4b14-f4d2-4177-b27a-4c04bc7fdb9f

📥 Commits

Reviewing files that changed from the base of the PR and between a95d267 and b23ad8e.

📒 Files selected for processing (4)
  • lib/crewai-tools/src/crewai_tools/tools/youtube_channel_search_tool/youtube_channel_search_tool.py
  • lib/crewai-tools/tests/tools/test_youtube_channel_search_tool.py
  • lib/crewai/src/crewai/llms/providers/openai/completion.py
  • lib/crewai/tests/llms/openai/test_openai.py

Comment on lines 45 to +50
if not youtube_channel_handle.startswith("@"):
youtube_channel_handle = f"@{youtube_channel_handle}"
super().add(youtube_channel_handle, data_type=DataType.YOUTUBE_CHANNEL)
# Convert handle to a full YouTube URL so downstream loaders receive
# a valid channel URL instead of a bare handle.
channel_url = f"https://www.youtube.com/{youtube_channel_handle}"
super().add(channel_url, data_type=DataType.YOUTUBE_CHANNEL)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Handle full YouTube URLs before @ normalization.

Line 45 currently assumes non-@ input is always a handle. A fully-qualified URL gets corrupted and becomes invalid.

Proposed fix
     def add(  # type: ignore[override]
         self,
         youtube_channel_handle: str,
     ) -> None:
-        if not youtube_channel_handle.startswith("@"):
-            youtube_channel_handle = f"@{youtube_channel_handle}"
-        # Convert handle to a full YouTube URL so downstream loaders receive
-        # a valid channel URL instead of a bare handle.
-        channel_url = f"https://www.youtube.com/{youtube_channel_handle}"
+        # Accept fully-qualified URLs as-is; only normalize bare handles.
+        if youtube_channel_handle.startswith(("http://", "https://")):
+            channel_url = youtube_channel_handle
+        else:
+            if not youtube_channel_handle.startswith("@"):
+                youtube_channel_handle = f"@{youtube_channel_handle}"
+            # Convert handle to a full YouTube URL so downstream loaders receive
+            # a valid channel URL instead of a bare handle.
+            channel_url = f"https://www.youtube.com/{youtube_channel_handle}"
         super().add(channel_url, data_type=DataType.YOUTUBE_CHANNEL)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if not youtube_channel_handle.startswith("@"):
youtube_channel_handle = f"@{youtube_channel_handle}"
super().add(youtube_channel_handle, data_type=DataType.YOUTUBE_CHANNEL)
# Convert handle to a full YouTube URL so downstream loaders receive
# a valid channel URL instead of a bare handle.
channel_url = f"https://www.youtube.com/{youtube_channel_handle}"
super().add(channel_url, data_type=DataType.YOUTUBE_CHANNEL)
# Accept fully-qualified URLs as-is; only normalize bare handles.
if youtube_channel_handle.startswith(("http://", "https://")):
channel_url = youtube_channel_handle
else:
if not youtube_channel_handle.startswith("@"):
youtube_channel_handle = f"@{youtube_channel_handle}"
# Convert handle to a full YouTube URL so downstream loaders receive
# a valid channel URL instead of a bare handle.
channel_url = f"https://www.youtube.com/{youtube_channel_handle}"
super().add(channel_url, data_type=DataType.YOUTUBE_CHANNEL)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@lib/crewai-tools/src/crewai_tools/tools/youtube_channel_search_tool/youtube_channel_search_tool.py`
around lines 45 - 50, The code currently always prepends "@" to
youtube_channel_handle then builds channel_url, which corrupts full URLs; update
the logic in the method that sets youtube_channel_handle (the variable used
before calling super().add) to first detect if the input is already a full URL
(e.g., starts with "http://" or "https://" or common YouTube hosts like
"www.youtube.com" / "youtube.com" / "youtu.be") and, if so, use it as
channel_url directly; otherwise normalize bare handles by ensuring they start
with "@" and then build channel_url =
f"https://www.youtube.com/{youtube_channel_handle}" before calling
super().add(..., data_type=DataType.YOUTUBE_CHANNEL).

Comment on lines +1926 to 1934
try:
reasoning = chunk_delta.reasoning_content or ""
except AttributeError:
reasoning = ""
if chunk_delta.content or reasoning:
full_response += chunk_delta.content or reasoning
self._emit_stream_chunk_event(
chunk=chunk_delta.content,
chunk=chunk_delta.content or reasoning or "",
from_task=from_task,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Streaming fallback currently mixes reasoning and final content.

When reasoning_content arrives before content, Line 1931 and Line 2242 append reasoning into full_response, then later append actual content too. Non-streaming (Line 1688/Line 2089) returns content-first, so streaming becomes inconsistent and may leak reasoning text unexpectedly. Buffer reasoning separately and only use it if no content was emitted by stream end.

💡 Suggested direction
- full_response = ""
+ full_response = ""
+ reasoning_fallback = ""
+ saw_content = False

- if chunk_delta.content or reasoning:
-     full_response += chunk_delta.content or reasoning
-     self._emit_stream_chunk_event(chunk=chunk_delta.content or reasoning or "", ...)
+ if chunk_delta.content:
+     saw_content = True
+     full_response += chunk_delta.content
+     self._emit_stream_chunk_event(chunk=chunk_delta.content, ...)
+ elif reasoning:
+     reasoning_fallback += reasoning

# before finalize:
+ if not saw_content and reasoning_fallback:
+     full_response = reasoning_fallback
+     self._emit_stream_chunk_event(chunk=reasoning_fallback, ...)

Also applies to: 2237-2245

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/crewai/src/crewai/llms/providers/openai/completion.py` around lines 1926
- 1934, The streaming fallback mixes reasoning and final content because the
block using chunk_delta.reasoning_content and chunk_delta.content appends
reasoning into full_response and emits it immediately; later the actual content
may be appended again causing reasoning to leak. Fix by buffering reasoning
separately (e.g., local var reasoning_buffer) when processing chunks in the
streaming path and only append or emit that buffered reasoning if no content was
emitted by the end of the stream; update the logic around full_response,
chunk_delta.content, chunk_delta.reasoning_content and the
_emit_stream_chunk_event calls so emitted chunks prefer content and only fall
back to the buffered reasoning when final content was never produced.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[ Bug]: YoutubeChannelSearchTool Fails for Valid Channel Inputs

1 participant