-
Notifications
You must be signed in to change notification settings - Fork 581
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: react agent integration #179
base: master
Are you sure you want to change the base?
Changes from 6 commits
89730ca
294fe26
0884cd5
97f7ace
8917b15
689adc8
ed4eb12
4addce4
50a683b
6cdd5f1
a4caa38
daa79b7
c5c7645
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,222 @@ | ||
# =========== Copyright 2023 @ CAMEL-AI.org. All Rights Reserved. =========== | ||
# Licensed under the Apache License, Version 2.0 (the “License”); | ||
# you may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an “AS IS” BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
# =========== Copyright 2023 @ CAMEL-AI.org. All Rights Reserved. =========== | ||
from typing import Any, Dict, List, Optional, Tuple | ||
|
||
from colorama import Fore | ||
import re | ||
|
||
from camel.agents import ChatAgent, BaseToolAgent | ||
from camel.agents.chat_agent import ChatAgentResponse | ||
from camel.messages import ChatMessage, SystemMessage | ||
from camel.typing import ModelType | ||
from camel.utils import print_text_animated | ||
|
||
|
||
REACT_PROMPT_INSTRUCT = """ | ||
For each time of running, you should firstly give a thought about what action to be taken next for solving the target task, and then give an action to be executed. | ||
|
||
You should use Thought to describe your thoughts about your plan to obtain information related to the target task. | ||
And you should use Action to indicate what actions should be done, and then return. | ||
Each action statement should be in the format: Action[<action_input>] | ||
You do not need to output any observation! The observations will be obtained by the external tool executing the action you selected and be provided to you. | ||
When there are multiple search-related actions available, consider using others when one search engine fails. | ||
|
||
Your available actions are: | ||
|
||
Finish[<argument>] | ||
- input <argument>: any value to be returned | ||
- This action finishes this task and returns any value specified by <argument> | ||
|
||
""" | ||
|
||
REACT_PROMPT_EXAMPLE = """ | ||
Remember: For each time of running, you should return after you output an action! | ||
|
||
Here are is an example session (this example is only for presentation and works only if the available actions include "Search") | ||
Task: Answer the question: What is the capital of France? | ||
Thought: To answer this question, I should search for information about France. | ||
Action: Search[France] | ||
|
||
At this point you should return. And you will be called again with this: | ||
|
||
Observation: France is a country. The capital is Paris. | ||
|
||
You then output: | ||
|
||
Thought: From the observation, the capital of France is Paris. | ||
Action: Finish[Paris] | ||
""" | ||
|
||
# Regular verifier of Action statements | ||
ACTION_RE = re.compile('^Action: *') | ||
|
||
# Color settings for logging | ||
TASK_COLOR = Fore.LIGHTYELLOW_EX | ||
THOUGHT_COLOR = Fore.LIGHTCYAN_EX | ||
ACTION_COLOR = Fore.LIGHTRED_EX | ||
OBSERVE_COLOR = Fore.LIGHTGREEN_EX | ||
|
||
|
||
def parse_action(act_str): | ||
# example act_str: Search[entity] | ||
print(f"Action string: {act_str}") | ||
action = act_str.split('[')[0] | ||
action_input = act_str[:-1].split('[')[1] | ||
return action, action_input | ||
|
||
|
||
class ReActAgent(ChatAgent): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In the nearest future we are going to move away from inheritance to a "part of" principle for agents. A chat agent must be a field of ReactAgent. |
||
r"""Class for managing conversions of a CAMEL agent following ReAct pattern. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Every introduced code must be covered by a test. |
||
|
||
Args: | ||
system_message (SystemMessage): The system message for the chat agent. | ||
model (ModelType, optional): The LLM model to use for generating | ||
responses. (default :obj:`ModelType.GPT_4`) | ||
model_config (Any, optional): Configuration options for the LLM model. | ||
(default: :obj:`None`) | ||
message_window_size (int, optional): The maximum number of previous | ||
messages to include in the context window. If `None`, no windowing | ||
is performed. (default: :obj:`None`) | ||
action_space (Dict[Any], optional): The action space for the ReAct | ||
agent. (default: :obj:`None`) | ||
verbose (bool, optional): Whether to print the critic's messages. | ||
""" | ||
|
||
def __init__( | ||
self, | ||
system_message: SystemMessage, | ||
model: ModelType = ModelType.GPT_4, | ||
model_config: Optional[Any] = None, | ||
message_window_size: Optional[int] = None, | ||
action_space: Dict[str, BaseToolAgent] = None, | ||
verbose: bool = False, | ||
) -> None: | ||
self.action_space = action_space | ||
|
||
action_space_prompt = self.get_action_space_prompt() | ||
init_prompt = '\n'.join([ | ||
REACT_PROMPT_INSTRUCT, | ||
action_space_prompt, | ||
REACT_PROMPT_EXAMPLE, | ||
]) | ||
system_message.content = init_prompt | ||
|
||
self.verbose = verbose | ||
super().__init__( | ||
system_message=system_message, | ||
model=model, | ||
model_config=model_config, | ||
message_window_size=message_window_size, | ||
) | ||
|
||
|
||
def get_action_space_prompt(self) -> str: | ||
r"""Returns the action space prompt. | ||
|
||
Returns: | ||
str: The action space prompt. | ||
""" | ||
tool_agents = set() | ||
for agent in self.action_space.values(): | ||
tool_agents.add(agent) | ||
|
||
return "\n".join(agent.description for agent in tool_agents) | ||
|
||
def parse_thought_and_action(self, | ||
response: ChatAgentResponse) -> Tuple[str,str]: | ||
response_msg = response.msg.content | ||
contents = [a.split(':')[-1].strip() for a in response_msg.split('\n')] | ||
return contents[0], contents[1] | ||
|
||
|
||
def step( | ||
self, | ||
input_message: ChatMessage, | ||
max_turns: int = 10, | ||
) -> Tuple[ChatMessage, bool, Dict[str, Any]]: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The signature of step does not follow the signature of the base class. If you want to introduce a new signature you must name the method differently There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sorry but this pattern was borrowed from There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please do not use EmbodiedAgent as a reference. There are some deficiencies in the code that we will fix soon. Please check your code with mypy and make sure you code does not introduce any warnings. Soon mypy will be made obligatory. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I do not totally agree with naming all the methods differently which will make it very difficult for the user to use. Just like PyTorch, every deviated There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The key feature of Module is polymorphism, a Module can be composed, the implementation can be abstracted away. In camel I do not see a single instance of polymorphic use of step() and reset(), implying it is not needed so far. |
||
r"""Performs a step in the conversation. | ||
|
||
Args: | ||
input_message (ChatMessage): The input message, | ||
**which should specify a task to do** | ||
|
||
Returns: | ||
Tuple[ChatMessage, bool, Dict[str, Any]]: A tuple | ||
containing the output messages, termination status, and | ||
additional information. | ||
""" | ||
response = super().step(input_message) | ||
|
||
if response.msgs is None or len(response.msgs) == 0: | ||
raise RuntimeError("Got None output messages.") | ||
if response.terminated: | ||
raise RuntimeError(f"{self.__class__.__name__} step failed.") | ||
|
||
if self.verbose: | ||
# print_text_animated(TASK_COLOR + f"> Task: {input_message.content}") | ||
print(TASK_COLOR + f"> Task: {input_message.content}") | ||
|
||
i = 0 | ||
content = input_message.content | ||
while i < max_turns: | ||
i += 1 | ||
content += f"\n{response.msg.content}" | ||
|
||
# Add the thought and action into the agent's conversation history | ||
self.update_messages( | ||
ChatMessage(role_name=self.role_name, role_type=self.role_type, | ||
meta_dict=self.system_message.meta_dict, | ||
role=self.system_message.role, | ||
content=response.msg.content) | ||
) | ||
|
||
# Parse the new output containing a pair of Thought and Action | ||
thought, action = self.parse_thought_and_action(response) | ||
action, action_input = parse_action(action) | ||
|
||
if self.verbose: | ||
# print_text_animated(THOUGHT_COLOR + f"> Thought: {thought}") | ||
# print_text_animated(ACTION_COLOR + f"> Action: {action}[{action_input}]") | ||
print(THOUGHT_COLOR + f"> Thought: {thought}") | ||
print(ACTION_COLOR + f"> Action: {action}[{action_input}]") | ||
|
||
# Terminate | ||
if action == 'Finish': | ||
break | ||
|
||
# operation argument is for ToolAgent providing multiple actions | ||
obs = str(self.action_space[action].step(action_input, operation=action)) | ||
if self.verbose: | ||
# print_text_animated(OBSERVE_COLOR + f"> Observation: {obs}") | ||
print(OBSERVE_COLOR + f"> Observation: {obs}") | ||
|
||
# TODO not sure which type of message should be used | ||
obs = ChatMessage( | ||
role_type=self.role_type, | ||
role_name=self.system_message.role_name, | ||
meta_dict=self.system_message.meta_dict, | ||
role=self.system_message.role, | ||
content=obs, | ||
) | ||
response = super().step(obs) | ||
|
||
|
||
# TODO: Handle failed cases | ||
# TODO: Note the output emits all previous messages which has already been added into the | ||
# Agent's conversation history in the previous loop. RolePlaying should not update the conversation | ||
# history by the output again. | ||
finish_msg = ChatMessage(role_name=self.role_name, role_type=self.role_type, | ||
meta_dict=input_message.meta_dict, role=input_message.role, | ||
content=content) | ||
return finish_msg, response.terminated, response.info |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
# =========== Copyright 2023 @ CAMEL-AI.org. All Rights Reserved. =========== | ||
# Licensed under the Apache License, Version 2.0 (the “License”); | ||
# you may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an “AS IS” BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
# =========== Copyright 2023 @ CAMEL-AI.org. All Rights Reserved. =========== | ||
import os | ||
from typing import Any, Optional | ||
|
||
from camel.agents.tool_agents import BaseToolAgent | ||
|
||
|
||
class GoogleToolAgent(BaseToolAgent): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Every introduced code must be covered by a test. |
||
def __init__( | ||
self, | ||
name: str, | ||
*args: Any, | ||
) -> None: | ||
try: | ||
from langchain.utilities import SerpAPIWrapper | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You are bringing in langchain as a dependency which is not reflected in requirements.txt There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In pyproject.toml, it is |
||
except ImportError: | ||
raise ValueError("Could not import langchain agents" | ||
"Please use pip install langchain") | ||
|
||
SAK = os.getenv('SERPAPI_KEY') | ||
|
||
# TODO to replace the LangChain wrapper with self-implemented one | ||
# Define LangHChain-implemented agent which can access Wiki | ||
self.search = SerpAPIWrapper(serpapi_api_key=SAK) | ||
|
||
self.name = name | ||
self.description = \ | ||
f""" | ||
GoogleSearch[<keyword>] | ||
- input <keyword>: the information to be searched using Google search engine | ||
- Keep a rule in mind: if the current search does not return valid page, try to make the search item more general and shorter, instead of making it finer | ||
- Example usage: GoogleSearch[What is the current weather of London?] | ||
- by which we can obtain the weather information in London through Google | ||
""" | ||
|
||
def reset(self) -> None: | ||
pass | ||
|
||
def step( | ||
self, | ||
question, | ||
**kwargs: Any, | ||
) -> Any: | ||
return self.search.run(question, **kwargs) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rename to lower case react_agent.py