Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
30 changes: 30 additions & 0 deletions src/google/adk/planners/base_planner.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

import abc
from abc import ABC
from typing import Any
from typing import Dict
from typing import List
from typing import Optional

Expand Down Expand Up @@ -66,3 +68,31 @@ def process_planning_response(
The processed response parts, or None if no processing is needed.
"""
pass

def to_content_blocks(
self,
response_parts: List[types.Part],
) -> List[Dict[str, Any]]:
"""Converts planner response parts to standardized content blocks.

Produces a representation compatible with the standard content block format
(similar to LangChain 1.0), where thought/reasoning parts are emitted as
``{'type': 'reasoning', 'reasoning': '...'}`` blocks and regular text parts
as ``{'type': 'text', 'text': '...'}`` blocks. Parts that are neither text
nor thought are skipped.

Args:
response_parts: The planner response parts to convert.

Returns:
A list of standardized content block dicts.
"""
blocks: List[Dict[str, Any]] = []
for part in response_parts:
if part.text is None:
continue
if part.thought:
blocks.append({'type': 'reasoning', 'reasoning': part.text})
else:
blocks.append({'type': 'text', 'text': part.text})
return blocks
62 changes: 62 additions & 0 deletions tests/unittests/planners/test_plan_re_act_planner.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

"""Tests for PlanReActPlanner.process_planning_response."""

from google.adk.planners.built_in_planner import BuiltInPlanner
from google.adk.planners.plan_re_act_planner import PlanReActPlanner
from google.genai import types

Expand Down Expand Up @@ -56,3 +57,64 @@ def test_preserves_parallel_function_calls_after_leading_text():
)

assert _function_call_names(result) == ["get_weather", "get_time"]


# ---------------------------------------------------------------------------
# Tests for BasePlanner.to_content_blocks (exercised via PlanReActPlanner and
# BuiltInPlanner which are the two concrete subclasses).
# ---------------------------------------------------------------------------


def test_to_content_blocks_text_and_reasoning():
"""Thought parts map to 'reasoning' blocks; plain text maps to 'text' blocks."""
planner = PlanReActPlanner()
response_parts = [
types.Part(text="I should check the weather first.", thought=True),
types.Part(text="Here is your answer."),
]

blocks = planner.to_content_blocks(response_parts)

assert blocks == [
{"type": "reasoning", "reasoning": "I should check the weather first."},
{"type": "text", "text": "Here is your answer."},
]


def test_to_content_blocks_empty_parts():
"""Empty input returns an empty list."""
planner = PlanReActPlanner()
assert planner.to_content_blocks([]) == []


def test_to_content_blocks_skips_non_text_parts():
"""Parts without text (e.g. function calls) are skipped."""
planner = PlanReActPlanner()
response_parts = [
types.Part(text="Some reasoning.", thought=True),
types.Part.from_function_call(name="get_weather", args={"city": "NY"}),
types.Part(text="Final answer."),
]

blocks = planner.to_content_blocks(response_parts)

assert blocks == [
{"type": "reasoning", "reasoning": "Some reasoning."},
{"type": "text", "text": "Final answer."},
]


def test_to_content_blocks_built_in_planner():
"""BuiltInPlanner inherits to_content_blocks correctly."""
planner = BuiltInPlanner(thinking_config=types.ThinkingConfig())
response_parts = [
types.Part(text="Thinking step.", thought=True),
types.Part(text="Response text."),
]

blocks = planner.to_content_blocks(response_parts)

assert blocks == [
{"type": "reasoning", "reasoning": "Thinking step."},
{"type": "text", "text": "Response text."},
]