Skip to content
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

Update the branch feature/role-generation from master #265

Merged
merged 5 commits into from
Aug 30, 2023
Merged
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
4 changes: 4 additions & 0 deletions camel/configs.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ def from_openai_function_list(
cls,
function_list: List[OpenAIFunction],
function_call: Union[Dict[str, str], str] = "auto",
kwargs: Optional[Dict[str, Any]] = None,
):
r"""Class method for creating an instance given the function-related
arguments.
Expand All @@ -118,6 +119,8 @@ def from_openai_function_list(
function_call (Union[Dict[str, str], str], optional): Controls how
the model responds to function calls, as specified in the
creator's documentation.
kwargs (Optional[Dict[str, Any]]): The extra modifications to be
made on the original settings defined in :obj:`ChatGPTConfig`.

Return:
FunctionCallingConfig: A new instance which loads the given
Expand All @@ -127,4 +130,5 @@ def from_openai_function_list(
return cls(
functions=[func.as_dict() for func in function_list],
function_call=function_call,
**(kwargs or {}),
)
99 changes: 19 additions & 80 deletions camel/functions/search_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,98 +13,37 @@
# =========== Copyright 2023 @ CAMEL-AI.org. All Rights Reserved. ===========
from typing import List

import requests
from bs4 import BeautifulSoup
import wikipedia

from .openai_function import OpenAIFunction


def clean_str(p: str) -> str:
r"""Cleans the input string by encoding and decoding it multiple times
to ensure it can be properly read and processed by Python code.

Args:
p (str): The input string to be cleaned, typically from the webpage.

Returns:
str: The cleaned string.
"""
return p.encode().decode("unicode-"
"escape").encode("latin1").decode("utf-8")


def get_page_abstract(page: str) -> str:
r"""Returns the first :obj:`5` sentences of the fetched page.

Args:
page (str): The fetched page.

Returns:
str: The concatenation of the first :obj:`5` sentences in the
given page.
"""
paragraphs = page.split('\n')
paragraphs = [p.strip() for p in paragraphs if p.strip()]

# find all sentences
sentences = []
for p in paragraphs:
sents = p.split('. ')
if sents[-1].endswith('.'):
sents[-1] = sents[-1][:-1]
sentences += sents
sentences = [s.strip() + '.' for s in sentences if s.strip()]

return ' '.join(sentences[:5])


def search_wiki(entity: str) -> str:
r"""Search the entity in WikiPedia and return (the first :obj:`5`
sentences of) the required page, containing factual information
about the given entity.
r"""Search the entity in WikiPedia and return the summary of the
required page, containing factual information about the given entity.

Args:
entity (string): The entity to be searched.

Returns:
string: The search result. If the page corresponding to the entity
exists, return the first :obj:`5` sentences in a string.
exists, return the summary of this entity in a string.
"""
entity_ = entity.replace(" ", "+")
search_url = f"https://en.wikipedia.org/w/index.php?search={entity_}"

# request the target page
response_text = requests.get(search_url).text

# parse the obtained page
soup = BeautifulSoup(response_text, features="html.parser")
result_divs = soup.find_all("div", {"class": "mw-search-result-heading"})

observation: str
if result_divs:
# only similar concepts exist
result_titles = [
clean_str(div.get_text().strip()) for div in result_divs
]
observation = (f"Could not find {entity}. "
f"Similar: {result_titles[:5]}.")
else:
# the page corresponding to the entity exists
page = [
p.get_text().strip()
for p in soup.find_all("p") + soup.find_all("ul")
]

res_page = ""
for p in page:
if len(p.split(" ")) > 2:
res_page += clean_str(p)
if not p.endswith("\n"):
res_page += "\n"

observation = get_page_abstract(res_page)

return observation
result: str

try:
result = wikipedia.summary(entity, sentences=5, auto_suggest=False)
except wikipedia.exceptions.DisambiguationError as e:
result = wikipedia.summary(e.options[0], sentences=5,
auto_suggest=False)
except wikipedia.exceptions.PageError:
result = ("There is no page in Wikipedia corresponding to entity "
f"{entity}, please specify another word to describe the"
" entity to be searched.")
except wikipedia.exceptions.WikipediaException as e:
result = f"An exception occurred during the search: {e}"

return result


SEARCH_FUNCS: List[OpenAIFunction] = [
Expand Down
18 changes: 0 additions & 18 deletions camel/societies/role_playing.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@
TaskSpecifyAgent,
)
from camel.agents.chat_agent import ChatAgentResponse
from camel.configs import FunctionCallingConfig
from camel.functions import OpenAIFunction
from camel.generators import SystemMessageGenerator
from camel.human import Human
from camel.messages import BaseMessage
Expand Down Expand Up @@ -73,9 +71,6 @@ class RolePlaying:
task specify meta dict with. (default: :obj:`None`)
output_language (str, optional): The language to be output by the
agents. (default: :obj:`None`)
assistant_functions (list, optional): List of
:obj:`OpenAIFunction` objects to be loaded. If not specified,
function calling will be disabled. (default: :obj:`None`)
"""

def __init__(
Expand All @@ -100,7 +95,6 @@ def __init__(
extend_sys_msg_meta_dicts: Optional[List[Dict]] = None,
extend_task_specify_meta_dict: Optional[Dict] = None,
output_language: Optional[str] = None,
assistant_functions: Optional[List[OpenAIFunction]] = None,
) -> None:
self.with_task_specify = with_task_specify
self.with_task_planner = with_task_planner
Expand All @@ -109,8 +103,6 @@ def __init__(
self.task_type = task_type
self.task_prompt = task_prompt

self.assistant_functions = assistant_functions

self.specified_task_prompt: Optional[TextPrompt] = None
self.init_specified_task_prompt(assistant_role_name, user_role_name,
task_specify_agent_kwargs,
Expand Down Expand Up @@ -286,14 +278,6 @@ def init_agents(
output_language (str, optional): The language to be output by the
agents.
"""
if self.assistant_functions is not None:
assistant_config = FunctionCallingConfig.from_openai_function_list(
function_list=self.assistant_functions,
function_call="auto",
)
else:
assistant_config = None

if self.model_type is not None:
if assistant_agent_kwargs is None:
assistant_agent_kwargs = {}
Expand All @@ -304,9 +288,7 @@ def init_agents(

self.assistant_agent = ChatAgent(
init_assistant_sys_msg,
model_config=assistant_config,
output_language=output_language,
function_list=self.assistant_functions,
**(assistant_agent_kwargs or {}),
)
self.assistant_sys_msg = self.assistant_agent.system_message
Expand Down
23 changes: 19 additions & 4 deletions examples/function_call/role_playing_with_function.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from colorama import Fore

from camel.agents.chat_agent import FunctionCallingRecord
from camel.configs import ChatGPTConfig, FunctionCallingConfig
from camel.functions import MATH_FUNCS, SEARCH_FUNCS
from camel.societies import RolePlaying
from camel.typing import ModelType
Expand All @@ -25,15 +26,29 @@
def main(model_type=ModelType.GPT_4) -> None:
task_prompt = ("Assuming the current year is 2023, estimate KAUST's "
"current age and then add 10 more years to this age.")

user_model_config = ChatGPTConfig(temperature=0.0)

function_list = [*MATH_FUNCS, *SEARCH_FUNCS]
assistant_model_config = FunctionCallingConfig.from_openai_function_list(
function_list=function_list,
kwargs=dict(temperature=0.0),
)

role_play_session = RolePlaying(
assistant_role_name="Searcher",
user_role_name="Professor",
assistant_agent_kwargs=dict(model=model_type),
user_agent_kwargs=dict(model=model_type),
assistant_agent_kwargs=dict(
model=model_type,
model_config=assistant_model_config,
function_list=function_list,
),
user_agent_kwargs=dict(
model=model_type,
model_config=user_model_config,
),
task_prompt=task_prompt,
with_task_specify=False,
task_specify_agent_kwargs=dict(model=model_type),
assistant_functions=[*MATH_FUNCS, *SEARCH_FUNCS],
)

print(
Expand Down
4 changes: 4 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ torch = { version = "^1", optional = true }
soundfile = { version = "^0", optional = true }
sentencepiece = { version = "^0", optional = true }
opencv-python = { version = "^4", optional = true }
wikipedia = { version = "^1", optional = true }

[tool.poetry.extras]
huggingface-agent = [
Expand All @@ -54,6 +55,7 @@ huggingface-agent = [
"soundfile",
"sentencepiece",
"opencv-python",
"wikipedia",
]

all = [
Expand All @@ -65,6 +67,7 @@ all = [
"soundfile",
"sentencepiece",
"opencv-python",
"wikipedia",
]

[tool.poetry.group.dev]
Expand Down Expand Up @@ -128,5 +131,6 @@ module = [
"database_connection",
"huggingface_hub",
"huggingface_hub.utils._errors",
"wikipedia",
]
ignore_missing_imports = true
31 changes: 31 additions & 0 deletions test/agents/test_role_playing.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
import pytest

from camel.agents import ChatAgent, CriticAgent
from camel.configs import FunctionCallingConfig
from camel.functions import MATH_FUNCS
from camel.human import Human
from camel.messages import BaseMessage
from camel.societies import RolePlaying
Expand Down Expand Up @@ -115,3 +117,32 @@ def test_role_playing_step(task_type, extend_sys_msg_meta_dicts,
assert isinstance(response.terminated, bool)
assert response.terminated is False
assert isinstance(response.info, dict)


@pytest.mark.model_backend
def test_role_playing_with_function():
function_list = [*MATH_FUNCS]
assistant_model_config = FunctionCallingConfig.from_openai_function_list(
function_list=function_list)

role_playing = RolePlaying(
assistant_role_name="AI Assistant",
assistant_agent_kwargs=dict(model=ModelType.GPT_3_5_TURBO,
model_config=assistant_model_config,
function_list=function_list),
user_role_name="AI User",
user_agent_kwargs=dict(model=ModelType.GPT_3_5_TURBO),
task_prompt="Perform the task",
task_specify_agent_kwargs=dict(model=ModelType.GPT_3_5_TURBO),
task_type=TaskType.AI_SOCIETY,
)

init_assistant_msg, _ = role_playing.init_chat()
assistant_response, user_response = role_playing.step(init_assistant_msg)
for response in (assistant_response, user_response):
assert isinstance(response.msgs, list)
assert len(response.msgs) == 1
assert isinstance(response.msgs[0], BaseMessage)
assert isinstance(response.terminated, bool)
assert response.terminated is False
assert isinstance(response.info, dict)
47 changes: 14 additions & 33 deletions test/functions/test_search_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,49 +11,30 @@
# See the License for the specific language governing permissions and
# limitations under the License.
# =========== Copyright 2023 @ CAMEL-AI.org. All Rights Reserved. ===========
from camel.functions.search_functions import (
clean_str,
get_page_abstract,
search_wiki,
)
import wikipedia


def test_clean_str():
input_str = "Some escaped string with unicode characters: \u2019"
expected_output = "Some escaped string with unicode characters: ’"
assert clean_str(input_str) == expected_output


def test_get_page_abstract():
input_page = "\n".join([
"This is the first sentence", "This is the second sentence",
"This is the third sentence", "This is the fourth sentence",
"This is the fifth sentence", "This is the sixth sentence"
])
expected_output = (
"This is the first sentence. This is the second sentence. "
"This is the third sentence. This is the fourth sentence. "
"This is the fifth sentence.")

assert get_page_abstract(input_page) == expected_output
from camel.functions.search_functions import search_wiki


def test_search_wiki_normal():
expected_output = (
"Erygia sigillata is a species of moth in the family Erebidae found "
"in Himachal Pradesh, Northern India.[2] The moth was officially "
"recognized and classified in 1892. This Erebinae-related article "
"is a stub. You can help Wikipedia by expanding it. Main "
"pageContentsCurrent eventsRandom articleAbout WikipediaContact "
"usDonate. HelpLearn to editCommunity portalRecent "
"changesUpload file.")

# Test that `search_wiki` returns the expected output
"in Himachal Pradesh, Northern India. The moth was officially "
"recognized and classified in 1892.")

assert search_wiki("Erygia sigillata") == expected_output


def test_search_wiki_not_found():
search_output = search_output = search_wiki(
"South Africa Women Football Team")
assert search_output.startswith(
"Could not find South Africa Women Football Team.")
"There is no page in Wikipedia corresponding to entity South Africa "
"Women Football Team, please specify another word to describe the "
"entity to be searched.")


def test_search_wiki_with_ambiguity():
expected_output = wikipedia.summary("New York (state)", sentences=5,
auto_suggest=False)
assert search_wiki("New York") == expected_output