Skip to content

Commit

Permalink
Merge branch 'microsoft:main' into fix-contrib-tests
Browse files Browse the repository at this point in the history
  • Loading branch information
maxim-saplin authored Jan 3, 2024
2 parents 8e6dfcb + fed9384 commit ad729bf
Show file tree
Hide file tree
Showing 8 changed files with 136 additions and 60 deletions.
2 changes: 1 addition & 1 deletion autogen/agentchat/groupchat.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ def _prepare_and_select_agents(self, last_speaker: Agent) -> Tuple[Optional[Agen
return agents[0], agents
elif not agents:
raise ValueError(
f"No agent can execute the function {self.messages[-1]['name']}. "
f"No agent can execute the function {self.messages[-1]['function_call']['name']}. "
"Please check the function_map of the agents."
)
# remove the last speaker from the list to avoid selecting the same speaker if allow_repeat_speaker is False
Expand Down
25 changes: 19 additions & 6 deletions autogen/oai/openai_utils.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import os
import json
import logging
import os
import tempfile
from pathlib import Path
from typing import List, Optional, Dict, Set, Union
import logging
from typing import Dict, List, Optional, Set, Union

from dotenv import find_dotenv, load_dotenv

try:
Expand Down Expand Up @@ -416,7 +417,8 @@ def config_list_from_json(
of acceptable values for that field, the configuration will still be considered a match.
Args:
env_or_file (str): The name of the environment variable or the filename containing the JSON data.
env_or_file (str): The name of the environment variable, the filename, or the environment variable of the filename
that containing the JSON data.
file_location (str, optional): The directory path where the file is located, if `env_or_file` is a filename.
filter_dict (dict, optional): A dictionary specifying the filtering criteria for the configurations, with
keys representing field names and values being lists or sets of acceptable values for those fields.
Expand All @@ -435,10 +437,21 @@ def config_list_from_json(
Returns:
List[Dict]: A list of configuration dictionaries that match the filtering criteria specified in `filter_dict`.
"""
json_str = os.environ.get(env_or_file)
if json_str:
env_str = os.environ.get(env_or_file)

if env_str:
# The environment variable exists. We should use information from it.
if os.path.exists(env_str):
# It is a file location, and we need to load the json from the file.
with open(env_str, "r") as file:
json_str = file.read()
else:
# Else, it should be a JSON string by itself.
json_str = env_str
config_list = json.loads(json_str)
else:
# The environment variable does not exist.
# So, `env_or_file` is a filename. We should use the file location.
config_list_path = os.path.join(file_location, env_or_file)
try:
with open(config_list_path) as json_file:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ const Header = ({ meta, link }: any) => {
return (
<div
key={index + "linkrow"}
className={`text-primary items-center hover:text-accent hover:bg-secondary px-1 pt-1 block text-sm font-medium `}
className={`text-primary items-center hover:text-accent px-1 pt-1 block text-sm font-medium `}
>
<Link
className="hover:text-accent h-full flex flex-col"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,15 @@ const ChatBox = ({
editable?: boolean;
}) => {
const session: IChatSession | null = useConfigStore((state) => state.session);
const queryInputRef = React.useRef<HTMLInputElement>(null);
const textAreaInputRef = React.useRef<HTMLTextAreaElement>(null);
const messageBoxInputRef = React.useRef<HTMLDivElement>(null);
const { user } = React.useContext(appContext);

const serverUrl = getServerUrl();
const deleteMsgUrl = `${serverUrl}/messages/delete`;

const [loading, setLoading] = React.useState(false);
const [text, setText] = React.useState("");
const [error, setError] = React.useState<IStatus | null>({
status: true,
message: "All good",
Expand All @@ -51,7 +52,7 @@ const ChatBox = ({
let pageHeight, chatMaxHeight;
if (typeof window !== "undefined") {
pageHeight = window.innerHeight;
chatMaxHeight = pageHeight - 300 + "px";
chatMaxHeight = pageHeight - 350 + "px";
}

const parseMessages = (messages: any) => {
Expand Down Expand Up @@ -265,28 +266,26 @@ const ChatBox = ({
}, 200);
}, [messages]);

const textAreaDefaultHeight = "50px";
// clear text box if loading has just changed to false and there is no error
React.useEffect(() => {
if (loading === false && queryInputRef.current) {
if (queryInputRef.current) {
// console.log("loading changed", loading, error);
if (loading === false && textAreaInputRef.current) {
if (textAreaInputRef.current) {
if (error === null || (error && error.status === false)) {
queryInputRef.current.value = "";
textAreaInputRef.current.value = "";
textAreaInputRef.current.style.height = textAreaDefaultHeight;
}
}
}
}, [loading]);

// scroll to queryInputRef on load
React.useEffect(() => {
// console.log("scrolling to query input");
// if (queryInputRef.current) {
// queryInputRef.current.scrollIntoView({
// behavior: "smooth",
// block: "center",
// });
// }
}, []);
if (textAreaInputRef.current) {
textAreaInputRef.current.style.height = textAreaDefaultHeight; // Reset height to shrink if text is deleted
const scrollHeight = textAreaInputRef.current.scrollHeight;
textAreaInputRef.current.style.height = `${scrollHeight}px`;
}
}, [text]);

const chatHistory = (messages: IChatMessage[] | null) => {
let history = "";
Expand Down Expand Up @@ -378,6 +377,23 @@ const ChatBox = ({
});
};

const handleTextChange = (
event: React.ChangeEvent<HTMLTextAreaElement>
): void => {
setText(event.target.value);
};

const handleKeyDown = (
event: React.KeyboardEvent<HTMLTextAreaElement>
): void => {
if (event.key === "Enter" && !event.shiftKey) {
if (textAreaInputRef.current && !loading) {
event.preventDefault();
getCompletion(textAreaInputRef.current.value);
}
}
};

return (
<div className="text-primary relative h-full rounded ">
<div
Expand Down Expand Up @@ -414,55 +430,53 @@ const ChatBox = ({
{editable && (
<div className="mt-2 p-2 absolute bg-primary bottom-0 w-full">
<div
className={`mt-2 rounded p-2 shadow-lg flex mb-1 gap-2 ${
className={`rounded p-2 shadow-lg flex mb-1 gap-2 ${
loading ? " opacity-50 pointer-events-none" : ""
}`}
>
{/* <input className="flex-1 p-2 ring-2" /> */}
<form
autoComplete="on"
className="flex-1 "
className="flex-1 relative"
onSubmit={(e) => {
e.preventDefault();
// if (queryInputRef.current && !loading) {
// getCompletion(queryInputRef.current?.value);
// }
}}
>
<input
<textarea
id="queryInput"
name="queryInput"
autoComplete="on"
onKeyDown={(e) => {
if (e.key === "Enter" && queryInputRef.current && !loading) {
getCompletion(queryInputRef.current?.value);
onKeyDown={handleKeyDown}
onChange={handleTextChange}
placeholder="Write message here..."
ref={textAreaInputRef}
className="flex items-center w-full resize-none text-gray-600 bg-white p-2 ring-2 rounded-sm pl-5 pr-16"
style={{ maxHeight: "120px", overflowY: "auto" }}
/>
<div
role={"button"}
style={{ width: "45px", height: "35px" }}
title="Send message"
onClick={() => {
if (textAreaInputRef.current && !loading) {
getCompletion(textAreaInputRef.current.value);
}
}}
ref={queryInputRef}
className="w-full text-gray-600 bg-white p-2 ring-2 rounded-sm"
/>
className="absolute right-3 bottom-2 bg-accent hover:brightness-75 transition duration-300 rounded cursor-pointer flex justify-center items-center"
>
{" "}
{!loading && (
<div className="inline-block ">
<PaperAirplaneIcon className="h-6 w-6 text-white " />{" "}
</div>
)}
{loading && (
<div className="inline-block ">
<Cog6ToothIcon className="text-white animate-spin rounded-full h-6 w-6" />
</div>
)}
</div>
</form>
<div
role={"button"}
onClick={() => {
if (queryInputRef.current && !loading) {
getCompletion(queryInputRef.current?.value);
}
}}
className="bg-accent hover:brightness-75 transition duration-300 rounded pt-2 px-5 "
>
{" "}
{!loading && (
<div className="inline-block ">
<PaperAirplaneIcon className="h-6 text-white inline-block" />{" "}
</div>
)}
{loading && (
<div className="inline-block ">
<Cog6ToothIcon className="relative -pb-2 text-white animate-spin inline-flex rounded-full h-6 w-6" />
</div>
)}
</div>
</div>{" "}
<div>
<div className="mt-2 text-xs text-secondary">
Expand Down
2 changes: 1 addition & 1 deletion samples/apps/autogen-studio/notebooks/tutorial.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"This notebook focuses on demonstrating capabilities of the autogen studio workflow python api. \n",
"\n",
"- Declarative Specification of an Agent Workflow \n",
"- Loading the specification and runnning the resulting agent\n",
"- Loading the specification and running the resulting agent\n",
"\n",
"\n",
"> Note: The notebook currently demonstrates support for a two agent setup. Support for GroupChat is currently in development."
Expand Down
28 changes: 28 additions & 0 deletions test/agentchat/test_function_call_groupchat.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,5 +71,33 @@ def get_random_number():
user_proxy.initiate_chat(manager, message="Let's start the game!")


def test_no_function_map():
dummy1 = autogen.UserProxyAgent(
name="User_proxy",
system_message="A human admin that will execute function_calls.",
human_input_mode="NEVER",
)

dummy2 = autogen.UserProxyAgent(
name="User_proxy",
system_message="A human admin that will execute function_calls.",
human_input_mode="NEVER",
)
groupchat = autogen.GroupChat(agents=[dummy1, dummy2], messages=[], max_round=7)
groupchat.messages = [
{
"role": "assistant",
"content": None,
"function_call": {"name": "get_random_number", "arguments": "{}"},
}
]
with pytest.raises(
ValueError,
match="No agent can execute the function get_random_number. Please check the function_map of the agents.",
):
groupchat._prepare_and_select_agents(dummy2)


if __name__ == "__main__":
test_function_call_groupchat()
test_no_function_map()
25 changes: 23 additions & 2 deletions test/oai/test_utils.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import os
import json
import pytest
import logging
import os
import tempfile
from unittest import mock
from unittest.mock import patch

import pytest

import autogen # noqa: E402
from autogen.oai.openai_utils import DEFAULT_AZURE_API_VERSION

Expand Down Expand Up @@ -87,13 +89,32 @@ def test_config_list_from_json():
config_list_2 = autogen.config_list_from_json("config_list_test")
assert config_list == config_list_2

# Test: the env variable is set to a file path with folder name inside.
config_list_3 = autogen.config_list_from_json(
tmp_file.name, filter_dict={"model": ["gpt", "gpt-4", "gpt-4-32k"]}
)
assert all(config.get("model") in ["gpt-4", "gpt"] for config in config_list_3)

del os.environ["config_list_test"]

# Test: using the `file_location` parameter.
config_list_4 = autogen.config_list_from_json(
os.path.basename(tmp_file.name),
file_location=os.path.dirname(tmp_file.name),
filter_dict={"model": ["gpt4", "gpt-4-32k"]},
)

assert all(config.get("model") in ["gpt4", "gpt-4-32k"] for config in config_list_4)

# Test: the env variable is set to a file path.
fd, temp_name = tempfile.mkstemp()
json.dump(config_list, os.fdopen(fd, "w+"), indent=4)
os.environ["config_list_test"] = temp_name
config_list_5 = autogen.config_list_from_json("config_list_test")
assert config_list_5 == config_list_2

del os.environ["config_list_test"]


def test_config_list_openai_aoai():
# Testing the functionality for loading configurations for different API types
Expand Down
2 changes: 1 addition & 1 deletion website/blog/2023-12-29-AgentDescriptions/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ Since `descriptions` serve a different purpose than `system_message`s, it is wor

- Avoid using the 1st or 2nd person perspective. Descriptions should not contain "I" or "You", unless perhaps "You" is in reference to the GroupChat / orchestrator
- Include any details that might help the orchestrator know when to call upon the agent
- Keep descriptions short (e.g., "A helpfupl AI assistant with strong natural language and Python coding skills.").
- Keep descriptions short (e.g., "A helpful AI assistant with strong natural language and Python coding skills.").

The main thing to remember is that **the description is for the benefit of the GroupChatManager, not for the Agent's own use or instruction**.

Expand Down

0 comments on commit ad729bf

Please sign in to comment.