Skip to content
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
10 changes: 0 additions & 10 deletions automon/integrations/google/gmail/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,6 @@ class AutomonLabels:
'resume': 'automon/resume',
'analyze': 'automon/analyze',
"retry": 'automon/retry',
"test": 'automon/test',
'error': 'automon/error',
'drafted': 'automon/drafted',
'relevant': 'automon/relevant',
"remote": 'automon/remote',
"welcome": 'automon/welcome',
Expand Down Expand Up @@ -70,14 +67,10 @@ def __init__(self):
self.sent = Label(name='SENT', id='SENT')
self.unread = Label(name='UNREAD', id='UNREAD')
self.trash = Label(name='TRASH', id='TRASH')
self.drafted = Label(name=self.labels.get('drafted'), color=self._color_default)

# allow auto reply
self.auto_reply_enabled = Label(name=self.labels.get('auto_reply_enabled'), color=self._color_enabled)

# issues encountered
self.error = Label(name=self.labels.get('error'), color=self._color_error)

# retry draft
self.retry = Label(name=self.labels.get('retry'), color=self._color_error)

Expand All @@ -87,9 +80,6 @@ def __init__(self):
# remote
self.remote = Label(name=self.labels.get('remote'), color=self._color_default)

# test
self.test = Label(name=self.labels.get('test'), color=self._color_enabled)

# need user input
self.user_action_required = Label(name=self.labels.get('user_action_required'), color=self._color_error)

Expand Down
116 changes: 63 additions & 53 deletions automon/integrations/google/gmail/tests/test_client_autoreply.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,29 @@
from automon.integrations.ollamaWrapper import OllamaClient
from automon.integrations.google.gemini import GoogleGeminiClient

DEBUG = False
INFO = False

LoggingClient.logging.getLogger('httpx').setLevel(ERROR)
LoggingClient.logging.getLogger('httpcore').setLevel(ERROR)
LoggingClient.logging.getLogger('automon.integrations.ollamaWrapper.client').setLevel(DEBUG)
LoggingClient.logging.getLogger('automon.integrations.ollamaWrapper.utils').setLevel(ERROR)
LoggingClient.logging.getLogger('automon.integrations.ollamaWrapper.chat').setLevel(ERROR)
LoggingClient.logging.getLogger('automon.integrations.requestsWrapper.client').setLevel(CRITICAL)
LoggingClient.logging.getLogger('automon.integrations.google.oauth.config').setLevel(ERROR)
LoggingClient.logging.getLogger('automon.integrations.google.gemini.client').setLevel(ERROR)
LoggingClient.logging.getLogger('automon.integrations.google.gemini.config').setLevel(ERROR)
LoggingClient.logging.getLogger('automon.integrations.google.gemini.client').setLevel(ERROR)
LoggingClient.logging.getLogger('automon.integrations.google.gmail.client').setLevel(ERROR)
LoggingClient.logging.getLogger('opentelemetry.instrumentation.instrumentor').setLevel(ERROR)

if INFO:
LoggingClient.logging.getLogger('automon.integrations.google.gemini.client').setLevel(INFO)
LoggingClient.logging.getLogger('automon.integrations.google.gmail.client').setLevel(INFO)

if DEBUG:
LoggingClient.logging.getLogger('automon.integrations.google.gemini.client').setLevel(DEBUG)
LoggingClient.logging.getLogger('automon.integrations.google.gmail.client').setLevel(DEBUG)

USE_OLLAMA = False
USE_GEMINI = True
CHAT_FOREVER = False
Expand Down Expand Up @@ -176,53 +187,59 @@ def main():

for _thread in email_search.threads:

print('.', end='')
# _thread = gmail.thread_get_automon('195da1cbcaa5573b')

_first = _thread.automon_message_first
_latest = _thread.automon_message_latest

if (labels.test in _first.automon_labels
):
_FOUND = True
print('test', end='')
break
print(f"{_thread.id} :: {_first.payload.get_header('subject')} :: {_first.automon_labels}")

if (labels.retry in _first.automon_labels
or labels.retry in _latest.automon_labels
):
if labels.resume in _first.automon_labels:

if labels.sent in _latest.automon_labels:
continue

if [x for x in _thread.messages
if labels.retry in x.automon_labels]:
_FOUND = True
print('retry', end='')
break

if (labels.draft in _latest.automon_labels
and labels.trash not in _latest.automon_labels
):
continue

if labels.sent not in _latest.automon_labels:
_sent = False
for _message in _thread.messages:
if labels.sent in _message.automon_labels:
_sent = True
elif (labels.draft not in _message.automon_labels
and labels.trash not in _message.automon_labels
):
_sent = False

if not _sent:
_FOUND = True
print('new', end='')
break
continue

if (labels.analyze in _first.automon_labels
):
_FOUND = True
print('analyze', end='')
break

if (labels.auto_reply_enabled in _first.automon_labels
or labels.auto_reply_enabled in _latest.automon_labels
and labels.sent not in _latest.automon_labels
if ((labels.auto_reply_enabled in _first.automon_labels
or labels.auto_reply_enabled in _latest.automon_labels)
and (labels.sent not in _latest.automon_labels)
):
_FOUND = True
print('auto', end='')
break

if (gmail._userId != _first.automon_from_email()
and _first.automon_from_email().lower() == _latest.automon_from_email().lower()):
_FOUND = True
print('new', end='')
break

if labels.resume in _first.automon_labels:
continue

if labels.draft in _latest.automon_labels:
continue

if labels.sent in _latest.automon_labels:
continue

if _FOUND:
print(' :)')
break
Expand All @@ -242,9 +259,7 @@ def main():
or labels.auto_reply_enabled in _message.automon_labels
):
_RETRY = True
gmail.messages_modify(id=_message.id,
removeLabelIds=[labels.retry,
labels.drafted])
gmail.messages_modify(id=_message.id)

# delete DRAFT
if labels.draft in _message.automon_labels:
Expand All @@ -256,7 +271,7 @@ def main():
email_selected = _thread
resume_selected = resume_search.messages[0]

resume = resume_selected.automon_attachments().attachments[0].body.automon_data_html_text
resume = resume_selected.automon_attachments().attachments[0].parts[0].body.automon_data_html_text

to = email_selected.automon_message_first.automon_from().get('value')
from_ = email_selected.automon_message_first.automon_to().get('value')
Expand All @@ -269,6 +284,9 @@ def main():
prompts_emails_all = []
for message in email_selected.messages:

if labels.draft in message.automon_labels:
continue

_message = f"{message.to_dict()}"

import re
Expand Down Expand Up @@ -311,21 +329,12 @@ def main():
GoogleGeminiClient.prompts.agent_machine_job_applicant,
)
prompts.append(
f"MUST NOT have a reply if last email is not from the sender of the first email. \n"
f"EXCLUDE any email subject line. \n"
f"EXCLUDE any internal thought process. \n"
f"EXCLUDE any chain of thought process. \n"
f"EXCLUDE any conversational parts. \n"
f"MUST write in plain english. \n"
f"MUST write in first person. \n"
f"MUST respond as if in a conversation. \n"
f"MUST provide only the body of the response. \n"
f"MUST check for a job description in the first email before continuing. \n"
f"\n\n"
f"Create a response. "
)

if labels.test in email_selected.automon_labels:
if labels.retry in email_selected.automon_message_first.automon_labels:
response, model = run_llm(prompts=prompts, chat=True)
else:
response, model = run_llm(prompts=prompts, chat=False)
Expand All @@ -349,18 +358,17 @@ def main():
draft_get = gmail.draft_get_automon(id=draft.id)

gmail.messages_modify(id=email_selected.id,
addLabelIds=[labels.drafted,
labels.unread,
],
addLabelIds=[labels.unread],
removeLabelIds=[labels.retry]
)

prompts = prompts_emails.copy()
prompts.append(
f"Respond only true or false, does any of the emails have the 'auto reply enabled' label name?"
)
response, model = run_llm(prompts)
if gemini.true_or_false(response.lower()):
if (labels.auto_reply_enabled in email_selected.automon_message_latest.automon_labels
and labels.sent not in email_selected.automon_message_latest.automon_labels
):
draft_sent = gmail.draft_send(draft=draft)
gmail.messages_modify(id=email_selected.automon_message_first.id,
addLabelIds=[labels.unread],
)

prompts = [prompts_emails[0]] + prompts_resume
prompts.append(
Expand All @@ -372,7 +380,7 @@ def main():

prompts = [prompts_emails[0]]
prompts.append(
f"Respond only true or false, is the job remote?"
f"Respond only true or false, is the job fully and completely remote, with no in-office days?"
)
response, model = run_llm(prompts)
if gemini.true_or_false(response.lower()):
Expand All @@ -382,6 +390,8 @@ def main():

except Exception as error:

gmail.config.refresh_token()

import traceback
llm_check = [
f"Tell me what the error from this stacktrace could be. \n"
Expand All @@ -396,7 +406,7 @@ def main():
draft_subject=f"Bug Report",
draft_body=bug_report,
draft_to=[email_error])
gmail.messages_modify(id=_draft.message.id, addLabelIds=[labels.error,
gmail.messages_modify(id=_draft.message.id, addLabelIds=[labels.retry,
labels.unread,
])

Expand Down
12 changes: 10 additions & 2 deletions automon/integrations/google/gmail/v1/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,7 @@ class LabelListVisibility:
class AutomonAttachments(DictUpdate):
attachments: ['MessagePart']

def __init__(self, attachments: ['MessagePart']):
def __init__(self, attachments: ['MessagePart'] = []):
super().__init__()

self.attachments = attachments
Expand Down Expand Up @@ -937,6 +937,8 @@ class MessagePart(DictUpdate):
def __init__(self):
super().__init__()

self.headers = []

def enhance(self):

if hasattr(self, 'body'):
Expand All @@ -950,9 +952,15 @@ def enhance(self):

return self

def automon_attachments(self):
def automon_attachments(self) -> AutomonAttachments:
if hasattr(self, 'parts'):
return AutomonAttachments(attachments=self.parts)
return AutomonAttachments()

def get_header(self, header: str) -> Headers:
for _header in self.headers:
if header.lower() in _header.name.lower():
return _header

def __repr__(self):
if getattr(self, 'filename') and getattr(self, 'mimeType'):
Expand Down
7 changes: 6 additions & 1 deletion automon/integrations/google/oauth/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -420,7 +420,12 @@ def userinfo(self):
credentials=self.credentials,
num_retries=30,
)
userinfo = service.userinfo().get().execute()
userinfo = None
while userinfo is None:
try:
userinfo = service.userinfo().get().execute()
except Exception as error:
logger.error(f'[GoogleAuthConfig] :: userinfo :: error :: {error=}')

user_info = service.userinfo().get().execute()
self.user_info = userinfo
Expand Down
6 changes: 4 additions & 2 deletions automon/integrations/ollamaWrapper/prompt_templates.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ class AgentTemplates:
@property
def agent_machine_job_applicant(self):
return (f"You are the person in the resume. \n"
f"MUST INCLUDE only experience from the provided resume. \n"
f"MUST Respond with the tone, the theme, and use of words from the resume. \n"
f"MUST Respond as a conversation instead of an email. \n"
f"When presenting code, text blocks, or any structured information, present the content directly without any surrounding markers, prefixes, or suffixes such as '```text', '```code', or similar indicators. The response should consist solely of the requested information, formatted for readability but without extraneous characters or formatting elements. \n"
f"Provide only the requested information, formatted appropriately for readability. Do not include greetings, acknowledgments, apologies, justifications, or any other text that is not directly part of the requested output. \n"
f"When generating an email response, ensure that the output *only* includes the body of the email. *Do not* include a subject line, greetings, or any extraneous information. The response should start directly with the content of the email body, formatted for readability. \n")
f"When generating an email response, ensure that the output *only* includes the body of the email. *Do not* include a subject line, greetings, or any extraneous information. The response should start directly with the content of the email body, formatted for readability. \n"
f"Compose a concise and professional email response to a recruiter expressing interest in a job opportunity. Highlight relevant skills and experience from the provided resume, focusing on alignment with the job description. Mention specific technologies and automation experience to demonstrate suitability for the role. Briefly inquire about the alignment of your skills with the recruiter's needs, aiming for a response length that is informative but not overly verbose. \n"
f"Never include a subject line in any response. Provide only the requested information, formatted appropriately for readability. Do not include greetings, acknowledgments, apologies, justifications, or any other text that is not directly part of the requested output. \n")
Loading