Repository: camel-ai/camel

Issue:
[Feature Request] Refactor ThinkingToolkit to test and support more tools
### Required prerequisites

- [x] I have searched the [Issue Tracker](https://github.com/camel-ai/camel/issues) and [Discussions](https://github.com/camel-ai/camel/discussions) that this hasn't already been reported. (+1 or comment there if it has.)
- [ ] Consider asking first in a [Discussion](https://github.com/camel-ai/camel/discussions/new).

### Motivation

like planning, reflection etc..

### Solution

_No response_

### Alternatives

_No response_

### Additional context

_No response_


Discussion:
Hi @Wendong-Fan , this sounds interesting. Please let me know if I can take this.
Hey @JoyceXu02 , can you look at other issues. I had some changes for this already so will add this too. Thanks!
> Hey [@JoyceXu02](https://github.com/JoyceXu02) , can you look at other issues. I had some changes for this already so will add this too. Thanks!

No problem. 



Patch:
diff --git a/camel/toolkits/thinking_toolkit.py b/camel/toolkits/thinking_toolkit.py
index 071ebe30a6..20a7f5e03c 100644
--- a/camel/toolkits/thinking_toolkit.py
+++ b/camel/toolkits/thinking_toolkit.py
@@ -22,11 +22,7 @@
 
 
 class ThinkingToolkit(BaseToolkit):
-    r"""A toolkit for recording thoughts during reasoning processes.
-
-    Attributes:
-        thoughts (List[str]): A list to store the recorded thoughts.
-    """
+    r"""A toolkit for recording thoughts during reasoning processes."""
 
     def __init__(
         self,
@@ -39,36 +35,196 @@ def __init__(
                 (default: :obj: `None`)
         """
         super().__init__(timeout=timeout)
+        self.plans: List[str] = []
+        self.hypotheses: List[str] = []
         self.thoughts: List[str] = []
+        self.contemplations: List[str] = []
+        self.critiques: List[str] = []
+        self.syntheses: List[str] = []
+        self.reflections: List[str] = []
+
+    def plan(self, plan: str) -> str:
+        r"""Use the tool to create a plan or strategy.
+        This tool is for outlining the approach or steps to be taken before
+        starting the actual thinking process.
+
+        Args:
+            plan (str): A forward-looking plan or strategy.
+
+        Returns:
+            str: The recorded plan.
+        """
+        try:
+            logger.debug(f"Plan: {plan}")
+            self.plans.append(plan)
+            return f"Plan: {plan}"
+
+        except Exception as e:
+            error_msg = f"Error recording plan: {e}"
+            logger.error(error_msg)
+            return error_msg
+
+    def hypothesize(self, hypothesis: str) -> str:
+        r"""Use the tool to form a hypothesis or make a prediction.
+        This tool is for making educated guesses or predictions based on
+        the plan, before detailed thinking.
+
+        Args:
+            hypothesis (str): A hypothesis or prediction to test.
+
+        Returns:
+            str: The recorded hypothesis.
+        """
+        try:
+            logger.debug(f"Hypothesis: {hypothesis}")
+            if not self.plans:
+                return "Consider creating a plan before forming hypotheses."
+            self.hypotheses.append(hypothesis)
+            return f"Hypothesis: {hypothesis}"
+
+        except Exception as e:
+            error_msg = f"Error recording hypothesis: {e}"
+            logger.error(error_msg)
+            return error_msg
 
     def think(self, thought: str) -> str:
         r"""Use the tool to think about something.
         It will not obtain new information or change the database, but just
-        append the thought to the log. Use it when complex reasoning or some
-        cache memory is needed.
+        append the thought to the log. Use it for initial thoughts and
+        observations during the execution of the plan.
 
         Args:
             thought (str): A thought to think about.
 
         Returns:
-            str: The full log of thoughts including the new thought.
+            str: The recorded thought.
         """
         try:
             logger.debug(f"Thought: {thought}")
+            if not self.plans:
+                return (
+                    "Consider creating a plan before thinking "
+                    "through the process."
+                )
             self.thoughts.append(thought)
-
-            thoughts = "\n".join([f"- {t}" for t in self.thoughts])
-            return f"Thoughts:\n{thoughts}"
+            return f"Thought: {thought}"
 
         except Exception as e:
             error_msg = f"Error recording thought: {e}"
             logger.error(error_msg)
             return error_msg
 
+    def contemplate(self, contemplation: str) -> str:
+        r"""Use the tool to deeply contemplate an idea or concept.
+        This tool is for deeper, more thorough exploration of thoughts,
+        considering multiple perspectives and implications. It's more
+        comprehensive than basic thinking but more focused than reflection.
+
+        Args:
+            contemplation (str): A deeper exploration of thoughts or concepts.
+
+        Returns:
+            str: The recorded contemplation.
+        """
+        try:
+            logger.debug(f"Contemplation: {contemplation}")
+            if not self.thoughts:
+                return (
+                    "Consider thinking about the topic before "
+                    "deep contemplation."
+                )
+            self.contemplations.append(contemplation)
+            return f"Contemplation: {contemplation}"
+
+        except Exception as e:
+            error_msg = f"Error recording contemplation: {e}"
+            logger.error(error_msg)
+            return error_msg
+
+    def critique(self, critique: str) -> str:
+        r"""Use the tool to critically evaluate current thoughts.
+        This tool is for identifying potential flaws, biases, or
+        weaknesses in the current thinking process.
+
+        Args:
+            critique (str): A critical evaluation of current thoughts.
+
+        Returns:
+            str: The recorded critique.
+        """
+        try:
+            logger.debug(f"Critique: {critique}")
+            if not self.contemplations:
+                return "Consider contemplating deeply before critiquing."
+            self.critiques.append(critique)
+            return f"Critique: {critique}"
+
+        except Exception as e:
+            error_msg = f"Error recording critique: {e}"
+            logger.error(error_msg)
+            return error_msg
+
+    def synthesize(self, synthesis: str) -> str:
+        r"""Use the tool to combine and integrate various thoughts.
+        This tool is for bringing together different thoughts, contemplations,
+        and critiques into a coherent understanding.
+
+        Args:
+            synthesis (str): An integration of multiple thoughts and insights.
+
+        Returns:
+            str: The recorded synthesis.
+        """
+        try:
+            logger.debug(f"Synthesis: {synthesis}")
+            if not self.critiques:
+                return "Consider critiquing thoughts before synthesizing."
+            self.syntheses.append(synthesis)
+            return f"Synthesis: {synthesis}"
+
+        except Exception as e:
+            error_msg = f"Error recording synthesis: {e}"
+            logger.error(error_msg)
+            return error_msg
+
+    def reflect(self, reflection: str) -> str:
+        r"""Use the tool to reflect on the entire process.
+        This tool is for final evaluation of the entire thinking process,
+        including plans, hypotheses, thoughts, contemplations, critiques,
+        and syntheses.
+
+        Args:
+            reflection (str): A comprehensive reflection on the process.
+
+        Returns:
+            str: The recorded reflection.
+        """
+        try:
+            logger.debug(f"Reflection: {reflection}")
+            if not self.syntheses:
+                return (
+                    "Consider synthesizing insights before final reflection."
+                )
+            self.reflections.append(reflection)
+            return f"Reflection: {reflection}"
+
+        except Exception as e:
+            error_msg = f"Error recording reflection: {e}"
+            logger.error(error_msg)
+            return error_msg
+
     def get_tools(self) -> List[FunctionTool]:
         r"""Get all tools in the toolkit.
 
         Returns:
             List[FunctionTool]: A list of tools.
         """
-        return [FunctionTool(self.think)]
+        return [
+            FunctionTool(self.plan),
+            FunctionTool(self.hypothesize),
+            FunctionTool(self.think),
+            FunctionTool(self.contemplate),
+            FunctionTool(self.critique),
+            FunctionTool(self.synthesize),
+            FunctionTool(self.reflect),
+        ]
diff --git a/examples/toolkits/thinking_toolkit.py b/examples/toolkits/thinking_toolkit.py
index fcbf77cbea..ad033f9571 100644
--- a/examples/toolkits/thinking_toolkit.py
+++ b/examples/toolkits/thinking_toolkit.py
@@ -11,26 +11,30 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 # ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
+
 from camel.agents import ChatAgent
 from camel.configs import ChatGPTConfig
 from camel.models import ModelFactory
 from camel.toolkits import ThinkingToolkit
 from camel.types import ModelPlatformType, ModelType
 
-# Initialize model and toolkit
+# Create a Model
+model_config_dict = ChatGPTConfig(temperature=0.0).as_dict()
 model = ModelFactory.create(
     model_platform=ModelPlatformType.DEFAULT,
     model_type=ModelType.DEFAULT,
-    model_config_dict=ChatGPTConfig().as_dict(),
+    model_config_dict=model_config_dict,
 )
 
+# Initialize the ThinkingToolkit
 thinking_toolkit = ThinkingToolkit()
 tools = thinking_toolkit.get_tools()
 
-# Create an agent with the thinking toolkit
+# Set up the ChatAgent with thinking capabilities
 sys_msg = (
-    "You are a helpful assistant that can think through problems "
-    "step by step."
+    "You are an assistant that can break down complex problems and think "
+    "through solutions step by step. Use the thinking toolkit to organize "
+    "your thoughts, reflect on the problem, and create plans."
 )
 
 agent = ChatAgent(
@@ -39,11 +43,15 @@
     tools=tools,
 )
 
-# Example usage
-usr_msg = (
-    "Help me solve: If a train travels at 60 mph, how long to"
-    "cover 180 miles?"
-)
+# Example: Problem solving with thinking toolkit
+print("\nExample: Problem solving with thinking toolkit")
+print("=" * 80)
+
+usr_msg = """
+Help me solve this math problem:
+If a train travels at 60 mph and needs to cover 300 miles, 
+with 3 stops of 15 minutes each, how long will the journey take?
+"""
 
 response = agent.step(usr_msg)
 print(response.msgs[0].content)
@@ -51,56 +59,89 @@
 print(response.info['tool_calls'])
 
 """
+Example: Problem Solving with Thinking Toolkit
 ===============================================================================
-The train will take 3 hours to cover 180 miles at a speed of 60 mph.
+The train's total journey time for traveling 300 miles at 60 mph, with 
+3 stops of 15 minutes each, is 5.75 hours. This consists of 5 hours of 
+travel time and 0.75 hours (or 45 minutes) of stop time. The conversion 
+of stop time from minutes to hours was explicitly noted for clarity.
 
-Tool calls:
+Tool Calls:
 [
     ToolCallingRecord(
-        tool_name='think',
+        tool_name='plan',
         args={
-            'thought': ('To find the time taken to cover a certain distance at'
-                      'a constant speed, we can use the formula: time = '
-                      'distance / speed. In this case, the distance is 180 '
-                      'miles and the speed is 60 mph.')
+            'plan': '1. Compute the travel time for 300 miles at 60 mph '
+                    'without stops.\n'
+                    '2. Determine the total stop time.\n'
+                    '3. Sum the travel time and stop time to get the total '
+                    'journey duration.'
         },
-        result=('Thoughts:\n- To find the time taken to cover a certain '
-               'distance at a constant speed, we can use the formula: time = '
-               'distance / speed. In this case, the distance is 180 miles and '
-               'the speed is 60 mph.'),
-        tool_call_id='call_M6QQ0ymwpVNtpXaacWLhWtqA'
+        result='Plan: 1. Compute the travel time for 300 miles at 60 mph '
+               'without stops.\n'
+               '2. Determine the total stop time.\n'
+               '3. Sum the travel time and stop time to get the total journey '
+               'duration.',
+        tool_call_id='call_kKYeTFLMGPf0mhimAZ8hapFk'
     ),
     ToolCallingRecord(
-        tool_name='think', 
+        tool_name='think',
         args={
-            'thought': ('Now, substituting the values into the formula: time ='
-                      '180 miles / 60 mph. This can be calculated to find the '
-                      'time in hours.')
+            'thought': 'Using the formula time = distance / speed, where '
+                       'distance = 300 miles and speed = 60 mph, we can '
+                       'determine the travel time.'
         },
-        result=('Thoughts:\n- To find the time taken to cover a certain '
-               'distance at a constant speed, we can use the formula: time = '
-               'distance / speed. In this case, the distance is 180 miles and '
-               'the speed is 60 mph.\n- Now, substituting the values into the '
-               'formula: time = 180 miles / 60 mph. This can be calculated to '
-               'find the time in hours.'),
-        tool_call_id='call_7CXZctOm3BbBaSq1YeCNql45'
+        result='Thought: Using the formula time = distance / speed, where '
+               'distance = 300 miles and speed = 60 mph, we can determine '
+               'the travel time.',
+        tool_call_id='call_t3DXWahikwhc8ps0y2GTE9ko'
     ),
     ToolCallingRecord(
         tool_name='think',
         args={
-            'thought': ('Calculating: time = 180 / 60 = 3 hours. Therefore, it'
-                      'will take the train 3 hours to cover 180 miles at a '
-                      'speed of 60 mph.')
+            'thought': 'The total stop time is calculated as: number of '
+                       'stops * time per stop, which is 3 * 15 minutes.'
+        },
+        result='Thought: The total stop time is calculated as: number of '
+               'stops * time per stop, which is 3 * 15 minutes.',
+        tool_call_id='call_MM1YlTPmiMhhiy6HWqraKh8E'
+    ),
+    ToolCallingRecord(
+        tool_name='hypothesize',
+        args={
+            'hypothesis': 'The travel time for 300 miles at 60 mph should '
+                          'be 5 hours.'
+        },
+        result='Hypothesis: The travel time for 300 miles at 60 mph should '
+               'be 5 hours.',
+        tool_call_id='call_F16dfESrJmUDwieYDA2aCheB'
+    ),
+    ToolCallingRecord(
+        tool_name='hypothesize',
+        args={
+            'hypothesis': 'The total stop time for 3 stops of 15 minutes '
+                          'each should be 45 minutes.'
+        },
+        result='Hypothesis: The total stop time for 3 stops of 15 minutes '
+               'each should be 45 minutes.',
+        tool_call_id='call_coxWcLPATfKNdiqQDz853pm4'
+    ),
+    ToolCallingRecord(
+        tool_name='synthesize',
+        args={
+            'synthesis': 'The total journey time for the train traveling '
+                         '300 miles at 60 mph, with 3 stops of 15 minutes '
+                         'each, is 5.75 hours. This includes 5 hours of '
+                         'travel time and 0.75 hours (or 45 minutes) of '
+                         'stop time. The conversion of stop time from '
+                         'minutes to hours was explicitly noted for clarity.'
         },
-        result=('Thoughts:\n- To find the time taken to cover a certain '
-               'distance at a constant speed, we can use the formula: time = '
-               'distance / speed. In this case, the distance is 180 miles and '
-               'the speed is 60 mph.\n- Now, substituting the values into the '
-               'formula: time = 180 miles / 60 mph. This can be calculated to '
-               'find the time in hours.\n- Calculating: time = 180 / 60 = 3 '
-               'hours. Therefore, it will take the train 3 hours to cover 180 '
-               'miles at a speed of 60 mph.'),
-        tool_call_id='call_n1SOosiFYvl2pO6yuq9dLhQb'
+        result='Synthesis: The total journey time for the train traveling '
+               '300 miles at 60 mph, with 3 stops of 15 minutes each, is '
+               '5.75 hours. This includes 5 hours of travel time and 0.75 '
+               'hours (or 45 minutes) of stop time. The conversion of stop '
+               'time from minutes to hours was explicitly noted for clarity.',
+        tool_call_id='call_9AHg54snm17XN7Mj1UzgSV04'
     )
 ]
 ===============================================================================


Test patch:
diff --git a/test/toolkits/test_thinking_toolkit.py b/test/toolkits/test_thinking_toolkit.py
index 83b71838ce..d492f565d0 100644
--- a/test/toolkits/test_thinking_toolkit.py
+++ b/test/toolkits/test_thinking_toolkit.py
@@ -11,6 +11,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 # ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
+
 import pytest
 
 from camel.toolkits import ThinkingToolkit
@@ -24,45 +25,178 @@ def thinking_toolkit():
 
 def test_toolkit_initialization(thinking_toolkit):
     r"""Test if the toolkit is initialized correctly."""
+    assert hasattr(thinking_toolkit, 'plans')
+    assert hasattr(thinking_toolkit, 'hypotheses')
     assert hasattr(thinking_toolkit, 'thoughts')
-    assert isinstance(thinking_toolkit.thoughts, list)
-    assert len(thinking_toolkit.thoughts) == 0
+    assert hasattr(thinking_toolkit, 'contemplations')
+    assert hasattr(thinking_toolkit, 'critiques')
+    assert hasattr(thinking_toolkit, 'syntheses')
+    assert hasattr(thinking_toolkit, 'reflections')
+
+    assert all(
+        isinstance(attr, list)
+        for attr in [
+            thinking_toolkit.plans,
+            thinking_toolkit.hypotheses,
+            thinking_toolkit.thoughts,
+            thinking_toolkit.contemplations,
+            thinking_toolkit.critiques,
+            thinking_toolkit.syntheses,
+            thinking_toolkit.reflections,
+        ]
+    )
+
+    assert all(
+        len(attr) == 0
+        for attr in [
+            thinking_toolkit.plans,
+            thinking_toolkit.hypotheses,
+            thinking_toolkit.thoughts,
+            thinking_toolkit.contemplations,
+            thinking_toolkit.critiques,
+            thinking_toolkit.syntheses,
+            thinking_toolkit.reflections,
+        ]
+    )
 
 
 def test_get_tools(thinking_toolkit):
     r"""Test getting available tools from ThinkingToolkit."""
     tools = thinking_toolkit.get_tools()
-    assert len(tools) == 1
-    assert tools[0].get_function_name() == "think"
+    assert len(tools) == 7
+    tool_names = {tool.get_function_name() for tool in tools}
+    assert tool_names == {
+        "plan",
+        "hypothesize",
+        "think",
+        "contemplate",
+        "critique",
+        "synthesize",
+        "reflect",
+    }
+
+
+def test_plan_method(thinking_toolkit):
+    r"""Test the plan method."""
+    plan = "This is a test plan"
+    result = thinking_toolkit.plan(plan)
+
+    assert isinstance(result, str)
+    assert "Plan:" in result
+    assert plan in result
+    assert len(thinking_toolkit.plans) == 1
+    assert thinking_toolkit.plans[0] == plan
+
+
+def test_hypothesize_method(thinking_toolkit):
+    r"""Test the hypothesize method."""
+    # Should suggest making a plan first
+    hypothesis = "Test hypothesis"
+    result = thinking_toolkit.hypothesize(hypothesis)
+    assert "plan" in result.lower()
+
+    # After making a plan
+    thinking_toolkit.plan("Test plan")
+    result = thinking_toolkit.hypothesize(hypothesis)
+    assert "Hypothesis:" in result
+    assert hypothesis in result
+    assert len(thinking_toolkit.hypotheses) == 1
+    assert thinking_toolkit.hypotheses[0] == hypothesis
 
 
 def test_think_method(thinking_toolkit):
     r"""Test the think method."""
-    thought = "This is a test thought"
+    # Should suggest making a plan first
+    thought = "Test thought"
     result = thinking_toolkit.think(thought)
+    assert "plan" in result.lower()
 
-    assert isinstance(result, str)
-    assert "Thoughts:" in result
+    # After making a plan
+    thinking_toolkit.plan("Test plan")
+    result = thinking_toolkit.think(thought)
+    assert "Thought:" in result
     assert thought in result
     assert len(thinking_toolkit.thoughts) == 1
     assert thinking_toolkit.thoughts[0] == thought
 
 
-def test_multiple_thoughts(thinking_toolkit):
-    r"""Test recording multiple thoughts."""
-    thoughts = ["First thought", "Second thought", "Third thought"]
-
-    for thought in thoughts:
-        result = thinking_toolkit.think(thought)
-        assert thought in result
-
-    assert len(thinking_toolkit.thoughts) == len(thoughts)
-    for thought in thoughts:
-        assert thought in thinking_toolkit.thoughts
-
+def test_contemplate_method(thinking_toolkit):
+    r"""Test the contemplate method."""
+    # Should suggest thinking first
+    contemplation = "Test contemplation"
+    result = thinking_toolkit.contemplate(contemplation)
+    assert "think" in result.lower()
+
+    # After planning and thinking
+    thinking_toolkit.plan("Test plan")
+    thinking_toolkit.think("Test thought")
+    result = thinking_toolkit.contemplate(contemplation)
+    assert "Contemplation:" in result
+    assert contemplation in result
+    assert len(thinking_toolkit.contemplations) == 1
+    assert thinking_toolkit.contemplations[0] == contemplation
+
+
+def test_critique_method(thinking_toolkit):
+    r"""Test the critique method."""
+    critique = "Test critique"
+    result = thinking_toolkit.critique(critique)
+    assert "contemplat" in result.lower()
+
+    # After proper setup
+    thinking_toolkit.plan("Test plan")
+    thinking_toolkit.think("Test thought")
+    thinking_toolkit.contemplate("Test contemplation")
+    result = thinking_toolkit.critique(critique)
+    assert "Critique:" in result
+    assert critique in result
+    assert len(thinking_toolkit.critiques) == 1
+    assert thinking_toolkit.critiques[0] == critique
+
+
+def test_synthesize_method(thinking_toolkit):
+    r"""Test the synthesize method."""
+    # Should suggest critiquing first
+    synthesis = "Test synthesis"
+    result = thinking_toolkit.synthesize(synthesis)
+    assert "critiquing" in result.lower()
+
+    # After proper setup
+    thinking_toolkit.plan("Test plan")
+    thinking_toolkit.think("Test thought")
+    thinking_toolkit.contemplate("Test contemplation")
+    thinking_toolkit.critique("Test critique")
+    result = thinking_toolkit.synthesize(synthesis)
+    assert "Synthesis:" in result
+    assert synthesis in result
+    assert len(thinking_toolkit.syntheses) == 1
+    assert thinking_toolkit.syntheses[0] == synthesis
+
+
+def test_reflect_method(thinking_toolkit):
+    r"""Test the reflect method."""
+    # Should suggest synthesizing first
+    reflection = "Test reflection"
+    result = thinking_toolkit.reflect(reflection)
+    assert "synthesiz" in result.lower()
+
+    # After proper setup
+    thinking_toolkit.plan("Test plan")
+    thinking_toolkit.think("Test thought")
+    thinking_toolkit.contemplate("Test contemplation")
+    thinking_toolkit.critique("Test critique")
+    thinking_toolkit.synthesize("Test synthesis")
+    result = thinking_toolkit.reflect(reflection)
+    assert "Reflection:" in result
+    assert reflection in result
+    assert len(thinking_toolkit.reflections) == 1
+    assert thinking_toolkit.reflections[0] == reflection
+
+
+def test_empty_inputs(thinking_toolkit):
+    r"""Test handling of empty inputs."""
+    thinking_toolkit.plan("Test plan")  # Setup prerequisite
 
-def test_empty_thought(thinking_toolkit):
-    r"""Test handling of empty thought."""
     result = thinking_toolkit.think("")
     assert isinstance(result, str)
     assert len(thinking_toolkit.thoughts) == 1