8000 fix: Filter out thought parts in lite_llm._get_content · google/adk-python@1ace8fc · GitHub 10BC0
[go: up one dir, main page]

Skip to content

Commit 1ace8fc

Browse files
GWealecopybara-github
authored andcommitted
fix: Filter out thought parts in lite_llm._get_content
Thought parts represent internal model reasoning and should not be included in the content sent back to the model in subsequent turns Close #3948 Co-authored-by: George Weale <gweale@google.com> PiperOrigin-RevId: 852965417
1 parent 084fcfa commit 1ace8fc

File tree

2 files changed

+60
-6
lines changed

2 files changed

+60
-6
lines changed

src/google/adk/models/lite_llm.py

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -584,9 +584,12 @@ async def _get_content(
584584
parts: Iterable[types.Part],
585585
*,
586586
provider: str = "",
587-
) -> Union[OpenAIMessageContent, str]:
587+
) -> OpenAIMessageContent:
588588
"""Converts a list of parts to litellm content.
589589
590+
Thought parts represent internal model reasoning and are always dropped so
591+
they are not replayed back to the model in subsequent turns.
592+
590593
Args:
591594
parts: The parts to convert.
592595
provider: The LLM provider name (e.g., "openai", "azure").
@@ -595,11 +598,25 @@ async def _get_content(
595598
The litellm content.
596599
"""
597600

601+
parts_without_thought = [part for part in parts if not part.thought]
602+
if len(parts_without_thought) == 1:
603+
part = parts_without_thought[0]
604+
if part.text:
605+
return part.text
606+
if (
607+
part.inline_data
608+
and part.inline_data.data
609+
and part.inline_data.mime_type
610+
and part.inline_data.mime_type.startswith("text/")
611+
):
612+
return _decode_inline_text_data(part.inline_data.data)
613+
598614
content_objects = []
599-
for part in parts:
615+
for part in parts_without_thought:
616+
# Skip thought parts to prevent reasoning from being replayed in subsequent
617+
# turns. Thought parts are internal model reasoning and should not be sent
618+
# back to the model.
600619
if part.text:
601-
if len(parts) == 1:
602-
return part.text
603620
content_objects.append({
604621
"type": "text",
605622
"text": part.text,
@@ -611,8 +628,6 @@ async def _get_content(
611628
):
612629
if part.inline_data.mime_type.startswith("text/"):
613630
decoded_text = _decode_inline_text_data(part.inline_data.data)
614-
if len(parts) == 1:
615-
return decoded_text
616631
content_objects.append({
617632
"type": "text",
618633
"text": decoded_text,

tests/unittests/models/test_litellm.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2086,6 +2086,45 @@ def test_split_message_content_prefers_existing_structured_calls():
20862086
assert tool_calls == [tool_call]
20872087

20882088

2089+
@pytest.mark.asyncio
2090+
async def test_get_content_filters_thought_parts():
2091+
"""Test that thought parts are filtered from content.
2092+
2093+
Thought parts contain model reasoning that should not be sent back to
2094+
the model in subsequent turns. This test verifies that _get_content
2095+
skips parts with thought=True.
< 10328 /code>2096+
2097+
See: https://github.com/google/adk-python/issues/3948
2098+
"""
2099+
# Create a thought part (reasoning) and a regular text part
2100+
thought_part = types.Part(text="Internal reasoning...", thought=True)
2101+
regular_part = types.Part.from_text(text="Visible response")
2102+
parts = [thought_part, regular_part]
2103+
2104+
content = await _get_content(parts)
2105+
2106+
# The thought part should be filtered out, leaving only the regular text
2107+
assert content == "Visible response"
2108+
2109+
2110+
@pytest.mark.asyncio
2111+
async def test_get_content_filters_all_thought_parts():
2112+
"""Test that all thought parts are filtered when only thoughts present.
2113+
2114+
When all parts are thought parts, _get_content should return an empty list.
2115+
2116+
See: https://github.com/google/adk-python/issues/3948
2117+
"""
2118+
thought_part1 = types.Part(text="First reasoning...", thought=True)
2119+
thought_part2 = types.Part(text="Second reasoning...", thought=True)
2120+
parts = [thought_part1, thought_part2]
2121+
2122+
content = await _get_content(parts)
2123+
2124+
# All thought parts should be filtered out
2125+
assert content == []
2126+
2127+
20892128
@pytest.mark.asyncio
20902129
async def test_get_content_text():
20912130
parts = [types.Part.from_text(text="Test text")]

0 commit comments

Comments
 (0)
0