Skip to content

Commit

Permalink
Check if each parameter was mentioned to the client when making modif…
Browse files Browse the repository at this point in the history
…ications
  • Loading branch information
rjambrecic committed Mar 7, 2024
1 parent 8082ebf commit 26854c2
Show file tree
Hide file tree
Showing 5 changed files with 152 additions and 21 deletions.
8 changes: 8 additions & 0 deletions captn/captn_agents/backend/function_configs.py
Original file line number Diff line number Diff line change
Expand Up @@ -765,6 +765,14 @@
"type": "string",
"description": """Explains which changes you want to make and why you want to make them.
I suggest adding new headline 'new-h' because it can increase the CTR and the number of conversions.
You MUST also tell about all the fields which will be effected by the changes, e.g.:
'status' will be changed from 'ENABLED' to 'PAUSED'
Budget will be set to 2$ ('cpc_bid_micros' will be changed from '1000000' to '2000000')
e.g. for AdGroupAd:
'final_url' will be set to 'https://my-web-page.com'
Hedlines will be extended wit a list 'hedlines' ['h1', 'h2', 'h3', 'new-h']
Do you approve the changes? To approve the changes, please answer 'Yes' and nothing else.""",
},
},
Expand Down
2 changes: 1 addition & 1 deletion captn/captn_agents/backend/functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ def get_info_from_the_web_page(
# code_execution_config=False,
llm_config=llm_config_gpt_3_5,
system_message=user_system_message,
iostream=iostream,
# iostream=iostream, # having some issues currently
)

initial_message = f"URL: {url}\nTASK: {task}"
Expand Down
5 changes: 5 additions & 0 deletions captn/captn_agents/backend/google_ads_team.py
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,7 @@ def _guidelines(self) -> str:
Currently we are in a demo phase and clients need to see what we are CURRENTLY able to do.
This is a template which you should follow when you are asked to optimize campaigns:
- The FIRST step should ALWAYS be listing the campaigns and asking the user in which one he is interested in. Do NOT try to analyse all campaigns at once!
- ad copy - Take a look at ad copy (headlines, descriptions, urls, (display) path1/path2...) and make suggestions on what should be changed (create/update/remove headlines etc.)
Headlines can have MAXIMUM 30 characters and description can have MAXIMUM 90 characters, NEVER suggest headlines/descriptions which exceed that length!
- keywords - analyse positive/negative keywords and find out which are (i)relevant for clients business and suggest some create/update/remove actions
Expand Down Expand Up @@ -387,6 +388,10 @@ def _commands(self) -> str:
2. read_file: Read an existing file, params: (filename: string)
3. ask_client_for_permission: Ask the client for permission to make the changes. Use this method before calling any of the modification methods!
params: (resource_details: string, proposed_changes: str)
'proposed_changes' parameter must contain info about each field which you want to modify and it MUST refernce it by the EXACT name as the one you are going to use in the modification method. e.g.:
if you want to update/set "budget_amount_micros" you must mention "budget_amount_micros" in this parameter.
same thing for "final_url", you must mention "final_url" in this parameter, if you mention "final url" or "final-url" it will NOT be accepted!
You MUST use this before you make ANY permanent changes. ALWAYS use this command before you make any changes and do NOT use 'reply_to_client' command for asking the client for the permission to make the changes!
ONLY Google ads specialist can suggest following commands:
Expand Down
55 changes: 50 additions & 5 deletions captn/google_ads/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,20 +125,60 @@ def get_user_ids_and_emails() -> str:
)


FIELDS_ARE_NOT_MENTIONED_ERROR_MSG = (
"The client must be informed abot ALL the changes that are going to be made!"
"The following fields were NOT mentioned in the approval question for the client. Please inform the client about the modifications of the following fields (use the EXACT names as the ones listed bellow e.g. if a field is called 'super_cool_field', you MUST reference it as 'super_cool_field'!):\n"
)

IGNORE_FIELDS = [
"update_existing_headline_index",
"update_existing_description_index",
"resource_type",
"location_ids",
]
FIELD_MAPPING = {
"keyword_text": "keyword",
"keyword_match_type": "match_type",
}


def check_fields_are_mentioned_to_the_client(
ad: BaseModel, modification_question: str
) -> str:
ad_dict = ad.model_dump()
error_msg = ""

# If the LLM generates "Final URL" the following line will replace it with "final_url"
modification_question_lower = modification_question.lower().replace(" ", "_")
for key, value in ad_dict.items():
if key in IGNORE_FIELDS:
continue
if value and not key.endswith("_id"):
key = FIELD_MAPPING.get(key, key)
if key not in modification_question_lower:
error_msg += f"'{key}' will be set to {value} (you MUST reference '{key}' in the 'proposed_changes' parameter!)\n"

if error_msg:
error_msg = FIELDS_ARE_NOT_MENTIONED_ERROR_MSG + error_msg

return error_msg


def _check_for_client_approval(
error_msg: str,
modification_question: str,
clients_approval_message: str,
clients_question_answere_list: List[Tuple[str, Optional[str]]],
) -> bool:
) -> str:
if (
modification_question,
clients_approval_message,
) not in clients_question_answere_list:
raise ValueError(NOT_IN_QUESTION_ANSWER_LIST)
error_msg += "\n\n" + NOT_IN_QUESTION_ANSWER_LIST
if clients_approval_message.lower().strip() != "yes":
raise ValueError(NOT_APPROVED)
error_msg += "\n\n" + NOT_APPROVED

return True
return error_msg


def google_ads_create_update(
Expand All @@ -150,11 +190,16 @@ def google_ads_create_update(
clients_question_answere_list: List[Tuple[str, Optional[str]]],
endpoint: str = "/update-ad-group-ad",
) -> Union[Dict[str, Any], str]:
_check_for_client_approval(
error_msg = check_fields_are_mentioned_to_the_client(ad, modification_question)
error_msg = _check_for_client_approval(
error_msg=error_msg,
clients_approval_message=clients_approval_message,
modification_question=modification_question,
clients_question_answere_list=clients_question_answere_list,
)
if error_msg:
raise ValueError(error_msg)

login_url_response = get_login_url(user_id=user_id, conv_id=conv_id)
if not login_url_response.get("login_url") == ALREADY_AUTHENTICATED:
return login_url_response
Expand Down
103 changes: 88 additions & 15 deletions tests/ci/test_client.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
from typing import List, Optional, Tuple

import pytest
from pydantic import BaseModel

from captn.google_ads.client import (
FIELDS_ARE_NOT_MENTIONED_ERROR_MSG,
NOT_APPROVED,
NOT_IN_QUESTION_ANSWER_LIST,
_check_for_client_approval,
check_fields_are_mentioned_to_the_client,
clean_error_response,
google_ads_create_update,
)


Expand All @@ -16,29 +22,96 @@ def test_clean_error_response() -> None:


def test_check_for_client_approval() -> None:
response = _check_for_client_approval(
error_msg = _check_for_client_approval(
error_msg="",
clients_approval_message="yes ",
modification_question="modification_question",
clients_question_answere_list=[("modification_question", "yes ")],
)
assert response
assert error_msg == ""


def test_check_for_client_approval_not_in_qa_list() -> None:
with pytest.raises(ValueError) as exc_info:
_check_for_client_approval(
clients_approval_message="yes",
modification_question="modification_question",
clients_question_answere_list=[("aa", "bb")],
)
assert exc_info.value.args[0] == NOT_IN_QUESTION_ANSWER_LIST
error_msg = _check_for_client_approval(
error_msg="",
clients_approval_message="yes",
modification_question="modification_question",
clients_question_answere_list=[("aa", "bb")],
)
assert error_msg.strip() == NOT_IN_QUESTION_ANSWER_LIST


def test_check_for_client_approval_client_did_not_approve() -> None:
with pytest.raises(ValueError) as exc_info:
_check_for_client_approval(
clients_approval_message="yes 123",
modification_question="modification_question",
clients_question_answere_list=[("modification_question", "yes 123")],
error_msg = _check_for_client_approval(
error_msg="",
clients_approval_message="yes 123",
modification_question="modification_question",
clients_question_answere_list=[("modification_question", "yes 123")],
)
assert error_msg.strip() == NOT_APPROVED


class AdTest(BaseModel):
ad_id: Optional[int] = None
ad_name: str
status: str


def test_check_fields_are_mentioned_to_the_client_returns_error_msg() -> None:
modification_question = "Do you approve changing the ad_name to test?"
ad = AdTest(ad_name="test", status="enabled")
error_msg = check_fields_are_mentioned_to_the_client(ad, modification_question)
assert error_msg == (
FIELDS_ARE_NOT_MENTIONED_ERROR_MSG
+ "'status' will be set to enabled (you MUST reference 'status' in the 'proposed_changes' parameter!)\n"
)


def test_check_fields_are_mentioned_to_the_client_returns_error_msg_for_two_fields() -> (
None
):
modification_question = "Do you approve?"
ad = AdTest(ad_name="test", status="enabled")
error_msg = check_fields_are_mentioned_to_the_client(ad, modification_question)
assert error_msg == (
FIELDS_ARE_NOT_MENTIONED_ERROR_MSG
+ "'ad_name' will be set to test (you MUST reference 'ad_name' in the 'proposed_changes' parameter!)\n"
+ "'status' will be set to enabled (you MUST reference 'status' in the 'proposed_changes' parameter!)\n"
)


def test_check_fields_are_mentioned_to_the_client_returns_empty_string_when_everything_is_ok() -> (
None
):
modification_question = (
"Do you approve changing the ad_name to test and status to enabled?"
)
ad = AdTest(ad_name="test", status="enabled")
error_msg = check_fields_are_mentioned_to_the_client(ad, modification_question)
assert error_msg == ""


def test_google_ads_create_update_raises_error() -> None:
clients_question_answere_list: List[Tuple[str, Optional[str]]] = [
("modification_question", "yes")
]
modification_question = "Do you approve?"
clients_approval_message = "yes"
ad = AdTest(ad_name="test", status="enabled")
with pytest.raises(ValueError) as e:
google_ads_create_update(
user_id=-1,
conv_id=-1,
clients_approval_message=clients_approval_message,
modification_question=modification_question,
ad=ad,
clients_question_answere_list=clients_question_answere_list,
)
assert exc_info.value.args[0] == NOT_APPROVED

assert e.value.args[0] == (
FIELDS_ARE_NOT_MENTIONED_ERROR_MSG
+ "'ad_name' will be set to test (you MUST reference 'ad_name' in the 'proposed_changes' parameter!)\n"
+ "'status' will be set to enabled (you MUST reference 'status' in the 'proposed_changes' parameter!)\n"
+ "\n\n"
+ NOT_IN_QUESTION_ANSWER_LIST
)

0 comments on commit 26854c2

Please sign in to comment.