From 8061176ec61f31c236bf886edd29acf2fba708b7 Mon Sep 17 00:00:00 2001 From: naisanza Date: Thu, 27 Mar 2025 03:20:20 -0500 Subject: [PATCH 1/4] [gmail] fixed no matches --- automon/integrations/google/gmail/client.py | 6 -- .../gmail/tests/test_client_autoreply.py | 78 +++++++++---------- .../ollamaWrapper/prompt_templates.py | 1 - 3 files changed, 39 insertions(+), 46 deletions(-) diff --git a/automon/integrations/google/gmail/client.py b/automon/integrations/google/gmail/client.py index 77de5f80..16fa4c0e 100644 --- a/automon/integrations/google/gmail/client.py +++ b/automon/integrations/google/gmail/client.py @@ -32,9 +32,7 @@ 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', @@ -70,7 +68,6 @@ 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) @@ -87,9 +84,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) diff --git a/automon/integrations/google/gmail/tests/test_client_autoreply.py b/automon/integrations/google/gmail/tests/test_client_autoreply.py index d1121def..5682398d 100644 --- a/automon/integrations/google/gmail/tests/test_client_autoreply.py +++ b/automon/integrations/google/gmail/tests/test_client_autoreply.py @@ -5,6 +5,9 @@ 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) @@ -12,11 +15,19 @@ 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 @@ -181,11 +192,11 @@ def main(): _first = _thread.automon_message_first _latest = _thread.automon_message_latest - if (labels.test in _first.automon_labels - ): - _FOUND = True - print('test', end='') - break + if labels.resume in _first.automon_labels: + continue + + if labels.sent in _latest.automon_labels: + continue if (labels.retry in _first.automon_labels or labels.retry in _latest.automon_labels @@ -200,29 +211,29 @@ def main(): 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.sent not in _latest.automon_labels: + _sent = False + for _message in _thread.messages: + if labels.sent in _message.automon_labels: + _sent = True - if labels.resume in _first.automon_labels: + if not _sent: + _FOUND = True + print('new', end='') + break continue if labels.draft in _latest.automon_labels: continue - if labels.sent in _latest.automon_labels: - continue - if _FOUND: print(' :)') break @@ -244,7 +255,7 @@ def main(): _RETRY = True gmail.messages_modify(id=_message.id, removeLabelIds=[labels.retry, - labels.drafted]) + labels.error]) # delete DRAFT if labels.draft in _message.automon_labels: @@ -256,7 +267,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') @@ -311,21 +322,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) @@ -349,18 +351,16 @@ 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], ) - 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( diff --git a/automon/integrations/ollamaWrapper/prompt_templates.py b/automon/integrations/ollamaWrapper/prompt_templates.py index eed0222c..a959f8c9 100644 --- a/automon/integrations/ollamaWrapper/prompt_templates.py +++ b/automon/integrations/ollamaWrapper/prompt_templates.py @@ -3,7 +3,6 @@ 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"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" From 7ff1fff11853c85c2b9bed35354ceca84af57252 Mon Sep 17 00:00:00 2001 From: naisanza Date: Sat, 29 Mar 2025 20:28:54 -0500 Subject: [PATCH 2/4] [oauth] fixed timeout when waiting for userinfo --- automon/integrations/google/gmail/client.py | 4 -- .../gmail/tests/test_client_autoreply.py | 45 +++++++++++-------- automon/integrations/google/oauth/config.py | 7 ++- .../ollamaWrapper/prompt_templates.py | 1 + 4 files changed, 34 insertions(+), 23 deletions(-) diff --git a/automon/integrations/google/gmail/client.py b/automon/integrations/google/gmail/client.py index 16fa4c0e..b039dadb 100644 --- a/automon/integrations/google/gmail/client.py +++ b/automon/integrations/google/gmail/client.py @@ -32,7 +32,6 @@ class AutomonLabels: 'resume': 'automon/resume', 'analyze': 'automon/analyze', "retry": 'automon/retry', - 'error': 'automon/error', 'relevant': 'automon/relevant', "remote": 'automon/remote', "welcome": 'automon/welcome', @@ -72,9 +71,6 @@ def __init__(self): # 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) diff --git a/automon/integrations/google/gmail/tests/test_client_autoreply.py b/automon/integrations/google/gmail/tests/test_client_autoreply.py index 5682398d..f6dfc575 100644 --- a/automon/integrations/google/gmail/tests/test_client_autoreply.py +++ b/automon/integrations/google/gmail/tests/test_client_autoreply.py @@ -187,7 +187,7 @@ def main(): for _thread in email_search.threads: - print('.', end='') + print(f" {_thread.id} ::", end='') _first = _thread.automon_message_first _latest = _thread.automon_message_latest @@ -198,6 +198,27 @@ def main(): if labels.sent in _latest.automon_labels: continue + 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.retry in _first.automon_labels or labels.retry in _latest.automon_labels ): @@ -219,21 +240,6 @@ def main(): print('auto', end='') break - if labels.sent not in _latest.automon_labels: - _sent = False - for _message in _thread.messages: - if labels.sent in _message.automon_labels: - _sent = True - - if not _sent: - _FOUND = True - print('new', end='') - break - continue - - if labels.draft in _latest.automon_labels: - continue - if _FOUND: print(' :)') break @@ -255,7 +261,7 @@ def main(): _RETRY = True gmail.messages_modify(id=_message.id, removeLabelIds=[labels.retry, - labels.error]) + ]) # delete DRAFT if labels.draft in _message.automon_labels: @@ -280,6 +286,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 @@ -396,7 +405,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, ]) diff --git a/automon/integrations/google/oauth/config.py b/automon/integrations/google/oauth/config.py index 2328d033..0254944c 100644 --- a/automon/integrations/google/oauth/config.py +++ b/automon/integrations/google/oauth/config.py @@ -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 diff --git a/automon/integrations/ollamaWrapper/prompt_templates.py b/automon/integrations/ollamaWrapper/prompt_templates.py index a959f8c9..35b960e7 100644 --- a/automon/integrations/ollamaWrapper/prompt_templates.py +++ b/automon/integrations/ollamaWrapper/prompt_templates.py @@ -4,6 +4,7 @@ class AgentTemplates: def agent_machine_job_applicant(self): return (f"You are the person in the 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") From 2e1b7b2b3139a7bff871e0630f415a36a45ce2d4 Mon Sep 17 00:00:00 2001 From: naisanza Date: Sat, 29 Mar 2025 20:45:40 -0500 Subject: [PATCH 3/4] [gmail] fixed finding header --- .../google/gmail/tests/test_client_autoreply.py | 4 ++-- automon/integrations/google/gmail/v1/__init__.py | 12 ++++++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/automon/integrations/google/gmail/tests/test_client_autoreply.py b/automon/integrations/google/gmail/tests/test_client_autoreply.py index f6dfc575..18e0f180 100644 --- a/automon/integrations/google/gmail/tests/test_client_autoreply.py +++ b/automon/integrations/google/gmail/tests/test_client_autoreply.py @@ -187,11 +187,11 @@ def main(): for _thread in email_search.threads: - print(f" {_thread.id} ::", end='') - _first = _thread.automon_message_first _latest = _thread.automon_message_latest + print(f"{_first.payload.get_header('subject')}") + if labels.resume in _first.automon_labels: continue diff --git a/automon/integrations/google/gmail/v1/__init__.py b/automon/integrations/google/gmail/v1/__init__.py index 626880b8..94b11961 100644 --- a/automon/integrations/google/gmail/v1/__init__.py +++ b/automon/integrations/google/gmail/v1/__init__.py @@ -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 @@ -937,6 +937,8 @@ class MessagePart(DictUpdate): def __init__(self): super().__init__() + self.headers = [] + def enhance(self): if hasattr(self, 'body'): @@ -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'): From 9f0f0458006c4c9c61229d4a2572655255c9fa5e Mon Sep 17 00:00:00 2001 From: naisanza Date: Sat, 29 Mar 2025 22:58:08 -0500 Subject: [PATCH 4/4] [gmail] fixed finding the retry label --- .../gmail/tests/test_client_autoreply.py | 31 ++++++++++--------- .../ollamaWrapper/prompt_templates.py | 4 ++- 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/automon/integrations/google/gmail/tests/test_client_autoreply.py b/automon/integrations/google/gmail/tests/test_client_autoreply.py index 18e0f180..e3f13ad4 100644 --- a/automon/integrations/google/gmail/tests/test_client_autoreply.py +++ b/automon/integrations/google/gmail/tests/test_client_autoreply.py @@ -187,16 +187,23 @@ def main(): for _thread in email_search.threads: + # _thread = gmail.thread_get_automon('195da1cbcaa5573b') + _first = _thread.automon_message_first _latest = _thread.automon_message_latest - print(f"{_first.payload.get_header('subject')}") + print(f"{_thread.id} :: {_first.payload.get_header('subject')} :: {_first.automon_labels}") if labels.resume in _first.automon_labels: - continue - if labels.sent in _latest.automon_labels: - continue + 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 @@ -219,13 +226,6 @@ def main(): break continue - if (labels.retry in _first.automon_labels - or labels.retry in _latest.automon_labels - ): - _FOUND = True - print('retry', end='') - break - if (labels.analyze in _first.automon_labels ): _FOUND = True @@ -259,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, - ]) + gmail.messages_modify(id=_message.id) # delete DRAFT if labels.draft in _message.automon_labels: @@ -361,6 +359,7 @@ def main(): gmail.messages_modify(id=email_selected.id, addLabelIds=[labels.unread], + removeLabelIds=[labels.retry] ) if (labels.auto_reply_enabled in email_selected.automon_message_latest.automon_labels @@ -381,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()): @@ -391,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" diff --git a/automon/integrations/ollamaWrapper/prompt_templates.py b/automon/integrations/ollamaWrapper/prompt_templates.py index 35b960e7..ed17a0a2 100644 --- a/automon/integrations/ollamaWrapper/prompt_templates.py +++ b/automon/integrations/ollamaWrapper/prompt_templates.py @@ -7,4 +7,6 @@ def agent_machine_job_applicant(self): 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")