Skip to content

Commit

Permalink
fixed Morgan's suggestions
Browse files Browse the repository at this point in the history
  • Loading branch information
ArtsiomWB committed Dec 11, 2023
1 parent b1c2471 commit 6c94681
Show file tree
Hide file tree
Showing 3 changed files with 51 additions and 85 deletions.
112 changes: 34 additions & 78 deletions src/wandbot/apps/zendesk/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
The system performs the following steps:
1. Creates a new ticket with a predefined question.
2. Enters a loop where it fetches new tickets every 120 seconds.
2. Enters a loop where it fetches new tickets every 600 seconds.
3. For each new ticket, it extracts the question, generates a response, formats the response, and updates the ticket
with the response.
Expand Down Expand Up @@ -41,9 +41,7 @@ def extract_question(ticket: Ticket) -> str:
This function performs the following steps:
1. Extracts the description from the ticket.
2. Converts the description to lower case and replaces newline and carriage return characters with a space.
3. Removes the string "[discourse post]" from the description.
4. Truncates the description to a maximum length of 4095 characters.
2. Chooses what type of ticket we are looking at, and then extracts the ticket depending on the ticket type
Args:
ticket (Ticket): The ticket object from which the question is to be extracted.
Expand All @@ -54,26 +52,23 @@ def extract_question(ticket: Ticket) -> str:

description = ticket.description
if "forum" in ticket.tags:
return discourseExt(description)
return discourse_ext(description)

elif "zopim_offline_message" in ticket.tags:
return offlineMessageExt(description)
return offline_msg_ext(description)

elif "add_cc_note" in ticket.tags:
return emailMsgExt(description)
return email_msg_ext(description)

return question


# TODO: add the necessary format we want to depending on ticket type
def format_response(response: str) -> str:
"""Formats the response to be sent as a ticket comment.
This function performs the following steps:
1. Converts the response to a string.
2. Truncates the response to a maximum length of 2000 characters.
3. Appends an ellipsis (...) if the response was truncated.
4. Appends a signature at the end of the response.
2. Appends a signature at the end of the response.
Args:
response (str): The response to be formatted.
Expand All @@ -82,13 +77,9 @@ def format_response(response: str) -> str:
str: The formatted response.
"""

response = str(response)
max_length = 2000
# Truncate the response to a maximum length of 2000 characters
if len(response) > max_length:
response = response[:max_length] + "..."
# Append a signature at the end of the response
return response + "\n\n-WandBot 🤖"
responseStr = str(response)
finalResponse = config.DISCBOTINTRO + responseStr
return finalResponse + "\n\n-WandBot 🤖"


class ZendeskAIResponseSystem:
Expand All @@ -100,6 +91,7 @@ class ZendeskAIResponseSystem:
Attributes:
zenpy_client (Zenpy): The client for interacting with the Zendesk API.
api_client (AsyncAPIClient): The client for interacting with the WandBot API.
semaphore (Semaphore): Use semaphore to control how many api calls to wandbot we make
"""

def __init__(self) -> None:
Expand All @@ -117,47 +109,8 @@ def __init__(self) -> None:
self.zenpy_client = Zenpy(**self.user_creds)
self.api_client = AsyncAPIClient(url=config.WANDBOT_API_URL)

def create_new_ticket(self, question_text: str) -> None:
"""Creates a new ticket in the Zendesk system.
This method uses the Zenpy client to create a new ticket in the Zendesk system. The ticket is created with a
predefined subject, status, priority, and tags. The description of the ticket is set to the provided question
text.
Args:
question_text (str): The text to be used as the description of the ticket.
Returns:
None
"""

self.zenpy_client.tickets.create(
Ticket(
subject="WandbotTest4",
description=question_text,
status="new",
priority="low",
tags=["botTest", "zopim_offline_message"],
)
)
self.zenpy_client.tickets.create(
Ticket(
subject="WandbotTest4",
description=question_text,
status="new",
priority="low",
tags=["botTest", "zopim_offline_message"],
)
)
self.zenpy_client.tickets.create(
Ticket(
subject="WandbotTest4",
description=question_text,
status="new",
priority="low",
tags=["botTest", "forum"],
)
)
self.semaphore = asyncio.Semaphore(config.MAX_WANDBOT_REQUESTS)
self.request_interval = config.REQUEST_INTERVAL

def fetch_new_tickets(self) -> List[Ticket]:
"""Fetches new tickets from the Zendesk system.
Expand Down Expand Up @@ -239,7 +192,6 @@ def gather_feedback(self, ticket: Ticket) -> None:
Args:
ticket (Ticket): The ticket for which feedback is to be gathered.
Returns:
None
"""
Expand All @@ -251,14 +203,11 @@ def gather_feedback(self, ticket: Ticket) -> None:
logger.error(f"Error: {e}")

async def run(self) -> None:
logger.info(f"WandBot + zendesk is running")

"""Runs the Zendesk AI Response System.
This method performs the following steps:
1. Creates a new ticket with a predefined question.
2. Enters a loop where it fetches new tickets every 120 seconds.
3. For each new ticket, it extracts the question, generates a response, formats the response,
1. Enters a loop where it fetches new tickets every 600 seconds.
2. For each new ticket, it extracts the question, generates a response, formats the response,
and updates the ticket with the response.
This method is asynchronous and should be run in an event loop.
Expand All @@ -267,29 +216,36 @@ async def run(self) -> None:
None
"""

# Create a new ticket with a predefined question
# self.create_new_ticket(
# "Is there a way to programmatically list all projects for a given entity?"
# )
# after semLimit number of tickets, have a timeout
semLimit = config.MAX_WANDBOT_REQUESTS
logger.info(f"WandBot + zendesk is running")
sem = asyncio.Semaphore(semLimit)

# Enter a loop where it fetches new tickets every 600 seconds
while True:
await asyncio.sleep(600)
await asyncio.sleep(config.INTERVAL_TO_FETCH_TICKETS)

# restart the zenpy client because it times out after 3 minutes
self.zenpy_client = Zenpy(**self.user_creds)

# Fetch new tickets
new_tickets = list(self.fetch_new_tickets())
logger.info(f"New unanswered Zendesk tickets: {new_tickets}")
# For each new ticket, extract the question, generate a response, format the response,
# and update the ticket with the response
for ticket in new_tickets:
question = extract_question(ticket)
response = await self.generate_response(question)

formatted_response = format_response(response)
self.update_ticket(ticket, formatted_response)
# For every semLimit new tickets, extract the question, generate a response, format the response,
# and update the ticket with the response
for i in range(0, len(new_tickets), semLimit):
batch = new_tickets[i : i + semLimit]
for ticket in batch:
async with sem:
question = extract_question(ticket)
response = await self.generate_response(question)

formatted_response = format_response(response)
self.update_ticket(ticket, formatted_response)

# Timeout after a certain amount of tickets, 5 in this case
if i + semLimit < len(new_tickets):
await asyncio.sleep(config.REQUEST_INTERVAL)

if len(new_tickets) > 0:
logger.info(f"Done processing tickets: {len(new_tickets)}")
Expand Down
9 changes: 9 additions & 0 deletions src/wandbot/apps/zendesk/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,20 @@


class ZendeskAppConfig(BaseSettings):
DISCBOTINTRO: str = (
"Thank you for reaching out to W&B Technical Support!\n\n"
f"This is an automated reply from our support bot designed to assist you with your WandB-related queries.\n"
f"If you find the solution unsatisfactory or have additional questions, we encourage you to contact our support team at [email protected], or continue replying in this thread\n\n"
)

ZENDESK_EMAIL: str = (Field(..., env="ZENDESK_EMAIL"),)
ZENDESK_PASSWORD: str = (Field(..., env="ZENDESK_PASSWORD"),)
ZENDESK_SUBDOMAIN: str = (Field(..., env="ZENDESK_SUBDOMAIN"),)
ZDGROUPID: str = ZDGROUPID
WANDBOT_API_URL: AnyHttpUrl = Field(..., env="WANDBOT_API_URL")
MAX_WANDBOT_REQUESTS: int = 5
REQUEST_INTERVAL: int = 60
INTERVAL_TO_FETCH_TICKETS: int = 600
include_sources: bool = True
bot_language: str = "en"

Expand Down
15 changes: 8 additions & 7 deletions src/wandbot/apps/zendesk/extract_by_type.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
def discourseExt(description) -> str:
question = description.lower().replace('\n', ' ').replace('\r', '')
question = question.replace('[discourse post]','')
question = question[:4095]
def discourse_ext(description) -> str:
question = description.lower().replace("\n", " ").replace("\r", "")
question = question.replace("[discourse post]", "")
return question

def offlineMessageExt(description) -> str:

def offline_msg_ext(description) -> str:
question = description.partition("Offline transcript:")[2]
return question

def emailMsgExt(description) -> str:

def email_msg_ext(description) -> str:
# its already clean but returning this for consistency
return description
return description

0 comments on commit 6c94681

Please sign in to comment.