initial commit

This commit is contained in:
2026-05-11 12:36:20 +05:30
commit 384cbe8019
15377 changed files with 2360544 additions and 0 deletions

View File

@@ -0,0 +1,19 @@
"""O365 tools."""
from langchain_community.tools.office365.create_draft_message import (
O365CreateDraftMessage,
)
from langchain_community.tools.office365.events_search import O365SearchEvents
from langchain_community.tools.office365.messages_search import O365SearchEmails
from langchain_community.tools.office365.send_event import O365SendEvent
from langchain_community.tools.office365.send_message import O365SendMessage
from langchain_community.tools.office365.utils import authenticate
__all__ = [
"O365SearchEmails",
"O365SearchEvents",
"O365CreateDraftMessage",
"O365SendMessage",
"O365SendEvent",
"authenticate",
]

View File

@@ -0,0 +1,20 @@
"""Base class for Office 365 tools."""
from __future__ import annotations
from typing import TYPE_CHECKING
from langchain_core.tools import BaseTool
from pydantic import Field
from langchain_community.tools.office365.utils import authenticate
if TYPE_CHECKING:
from O365 import Account
class O365BaseTool(BaseTool):
"""Base class for the Office 365 tools."""
account: Account = Field(default_factory=authenticate)
"""The account object for the Office 365 account."""

View File

@@ -0,0 +1,68 @@
from typing import List, Optional, Type
from langchain_core.callbacks import CallbackManagerForToolRun
from pydantic import BaseModel, Field
from langchain_community.tools.office365.base import O365BaseTool
class CreateDraftMessageSchema(BaseModel):
"""Input for SendMessageTool."""
body: str = Field(
...,
description="The message body to include in the draft.",
)
to: List[str] = Field(
...,
description="The list of recipients.",
)
subject: str = Field(
...,
description="The subject of the message.",
)
cc: Optional[List[str]] = Field(
None,
description="The list of CC recipients.",
)
bcc: Optional[List[str]] = Field(
None,
description="The list of BCC recipients.",
)
class O365CreateDraftMessage(O365BaseTool):
"""Tool for creating a draft email in Office 365."""
name: str = "create_email_draft"
description: str = (
"Use this tool to create a draft email with the provided message fields."
)
args_schema: Type[CreateDraftMessageSchema] = CreateDraftMessageSchema
def _run(
self,
body: str,
to: List[str],
subject: str,
cc: Optional[List[str]] = None,
bcc: Optional[List[str]] = None,
run_manager: Optional[CallbackManagerForToolRun] = None,
) -> str:
# Get mailbox object
mailbox = self.account.mailbox()
message = mailbox.new_message()
# Assign message values
message.body = body
message.subject = subject
message.to.add(to)
if cc is not None:
message.cc.add(cc)
if bcc is not None:
message.bcc.add(bcc)
message.save_draft()
output = "Draft created: " + str(message)
return output

View File

@@ -0,0 +1,127 @@
"""Util that Searches calendar events in Office 365.
Free, but setup is required. See link below.
https://learn.microsoft.com/en-us/graph/auth/
"""
from datetime import datetime as dt
from typing import Any, Dict, List, Optional, Type
from langchain_core.callbacks import CallbackManagerForToolRun
from pydantic import BaseModel, ConfigDict, Field
from langchain_community.tools.office365.base import O365BaseTool
from langchain_community.tools.office365.utils import UTC_FORMAT, clean_body
class SearchEventsInput(BaseModel):
"""Input for SearchEmails Tool.
From https://learn.microsoft.com/en-us/graph/search-query-parameter"""
start_datetime: str = Field(
description=(
" The start datetime for the search query in the following format: "
' YYYY-MM-DDTHH:MM:SS±hh:mm, where "T" separates the date and time '
" components, and the time zone offset is specified as ±hh:mm. "
' For example: "2023-06-09T10:30:00+03:00" represents June 9th, '
" 2023, at 10:30 AM in a time zone with a positive offset of 3 "
" hours from Coordinated Universal Time (UTC)."
)
)
end_datetime: str = Field(
description=(
" The end datetime for the search query in the following format: "
' YYYY-MM-DDTHH:MM:SS±hh:mm, where "T" separates the date and time '
" components, and the time zone offset is specified as ±hh:mm. "
' For example: "2023-06-09T10:30:00+03:00" represents June 9th, '
" 2023, at 10:30 AM in a time zone with a positive offset of 3 "
" hours from Coordinated Universal Time (UTC)."
)
)
max_results: int = Field(
default=10,
description="The maximum number of results to return.",
)
truncate: bool = Field(
default=True,
description=(
"Whether the event's body is truncated to meet token number limits. Set to "
"False for searches that will retrieve small events, otherwise, set to "
"True."
),
)
class O365SearchEvents(O365BaseTool):
"""Search calendar events in Office 365.
Free, but setup is required
"""
name: str = "events_search"
args_schema: Type[BaseModel] = SearchEventsInput
description: str = (
" Use this tool to search for the user's calendar events."
" The input must be the start and end datetimes for the search query."
" The output is a JSON list of all the events in the user's calendar"
" between the start and end times. You can assume that the user can "
" not schedule any meeting over existing meetings, and that the user "
"is busy during meetings. Any times without events are free for the user. "
)
model_config = ConfigDict(
extra="forbid",
)
def _run(
self,
start_datetime: str,
end_datetime: str,
max_results: int = 10,
truncate: bool = True,
run_manager: Optional[CallbackManagerForToolRun] = None,
truncate_limit: int = 150,
) -> List[Dict[str, Any]]:
# Get calendar object
schedule = self.account.schedule()
calendar = schedule.get_default_calendar()
# Process the date range parameters
start_datetime_query = dt.strptime(start_datetime, UTC_FORMAT)
end_datetime_query = dt.strptime(end_datetime, UTC_FORMAT)
# Run the query
q = calendar.new_query("start").greater_equal(start_datetime_query)
q.chain("and").on_attribute("end").less_equal(end_datetime_query)
events = calendar.get_events(query=q, include_recurring=True, limit=max_results)
# Generate output dict
output_events = []
for event in events:
output_event = {}
output_event["organizer"] = event.organizer
output_event["subject"] = event.subject
if truncate:
output_event["body"] = clean_body(event.body)[:truncate_limit]
else:
output_event["body"] = clean_body(event.body)
# Get the time zone from the search parameters
time_zone = start_datetime_query.tzinfo
# Assign the datetimes in the search time zone
output_event["start_datetime"] = event.start.astimezone(time_zone).strftime(
UTC_FORMAT
)
output_event["end_datetime"] = event.end.astimezone(time_zone).strftime(
UTC_FORMAT
)
output_event["modified_date"] = event.modified.astimezone(
time_zone
).strftime(UTC_FORMAT)
output_events.append(output_event)
return output_events

View File

@@ -0,0 +1,122 @@
"""Util that Searches email messages in Office 365.
Free, but setup is required. See link below.
https://learn.microsoft.com/en-us/graph/auth/
"""
from typing import Any, Dict, List, Optional, Type
from langchain_core.callbacks import CallbackManagerForToolRun
from pydantic import BaseModel, ConfigDict, Field
from langchain_community.tools.office365.base import O365BaseTool
from langchain_community.tools.office365.utils import UTC_FORMAT, clean_body
class SearchEmailsInput(BaseModel):
"""Input for SearchEmails Tool."""
"""From https://learn.microsoft.com/en-us/graph/search-query-parameter"""
folder: str = Field(
default="",
description=(
" If the user wants to search in only one folder, the name of the folder. "
'Default folders are "inbox", "drafts", "sent items", "deleted ttems", but '
"users can search custom folders as well."
),
)
query: str = Field(
description=(
"The Microsoift Graph v1.0 $search query. Example filters include "
"from:sender, from:sender, to:recipient, subject:subject, "
"recipients:list_of_recipients, body:excitement, importance:high, "
"received>2022-12-01, received<2021-12-01, sent>2022-12-01, "
"sent<2021-12-01, hasAttachments:true attachment:api-catalog.md, "
"cc:samanthab@contoso.com, bcc:samanthab@contoso.com, body:excitement date "
"range example: received:2023-06-08..2023-06-09 matching example: "
"from:amy OR from:david."
)
)
max_results: int = Field(
default=10,
description="The maximum number of results to return.",
)
truncate: bool = Field(
default=True,
description=(
"Whether the email body is truncated to meet token number limits. Set to "
"False for searches that will retrieve small messages, otherwise, set to "
"True"
),
)
class O365SearchEmails(O365BaseTool):
"""Search email messages in Office 365.
Free, but setup is required.
"""
name: str = "messages_search"
args_schema: Type[BaseModel] = SearchEmailsInput
description: str = (
"Use this tool to search for email messages."
" The input must be a valid Microsoft Graph v1.0 $search query."
" The output is a JSON list of the requested resource."
)
model_config = ConfigDict(
extra="forbid",
)
def _run(
self,
query: str,
folder: str = "",
max_results: int = 10,
truncate: bool = True,
run_manager: Optional[CallbackManagerForToolRun] = None,
truncate_limit: int = 150,
) -> List[Dict[str, Any]]:
# Get mailbox object
mailbox = self.account.mailbox()
# Pull the folder if the user wants to search in a folder
if folder != "":
mailbox = mailbox.get_folder(folder_name=folder)
# Retrieve messages based on query
query = mailbox.q().search(query)
messages = mailbox.get_messages(limit=max_results, query=query)
# Generate output dict
output_messages = []
for message in messages:
output_message = {}
output_message["from"] = message.sender
if truncate:
output_message["body"] = message.body_preview[:truncate_limit]
else:
output_message["body"] = clean_body(message.body)
output_message["subject"] = message.subject
output_message["date"] = message.modified.strftime(UTC_FORMAT)
output_message["to"] = []
for recipient in message.to._recipients:
output_message["to"].append(str(recipient))
output_message["cc"] = []
for recipient in message.cc._recipients:
output_message["cc"].append(str(recipient))
output_message["bcc"] = []
for recipient in message.bcc._recipients:
output_message["bcc"].append(str(recipient))
output_messages.append(output_message)
return output_messages

View File

@@ -0,0 +1,96 @@
"""Util that sends calendar events in Office 365.
Free, but setup is required. See link below.
https://learn.microsoft.com/en-us/graph/auth/
"""
from datetime import datetime as dt
from typing import List, Optional, Type
from zoneinfo import ZoneInfo
from langchain_core.callbacks import CallbackManagerForToolRun
from pydantic import BaseModel, Field
from langchain_community.tools.office365.base import O365BaseTool
from langchain_community.tools.office365.utils import UTC_FORMAT
class SendEventSchema(BaseModel):
"""Input for CreateEvent Tool."""
body: str = Field(
...,
description="The message body to include in the event.",
)
attendees: List[str] = Field(
...,
description="The list of attendees for the event.",
)
subject: str = Field(
...,
description="The subject of the event.",
)
start_datetime: str = Field(
description=" The start datetime for the event in the following format: "
' YYYY-MM-DDTHH:MM:SS±hh:mm, where "T" separates the date and time '
" components, and the time zone offset is specified as ±hh:mm. "
' For example: "2023-06-09T10:30:00+03:00" represents June 9th, '
" 2023, at 10:30 AM in a time zone with a positive offset of 3 "
" hours from Coordinated Universal Time (UTC).",
)
end_datetime: str = Field(
description=" The end datetime for the event in the following format: "
' YYYY-MM-DDTHH:MM:SS±hh:mm, where "T" separates the date and time '
" components, and the time zone offset is specified as ±hh:mm. "
' For example: "2023-06-09T10:30:00+03:00" represents June 9th, '
" 2023, at 10:30 AM in a time zone with a positive offset of 3 "
" hours from Coordinated Universal Time (UTC).",
)
class O365SendEvent(O365BaseTool):
"""Tool for sending calendar events in Office 365."""
name: str = "send_event"
description: str = (
"Use this tool to create and send an event with the provided event fields."
)
args_schema: Type[SendEventSchema] = SendEventSchema
def _run(
self,
body: str,
attendees: List[str],
subject: str,
start_datetime: str,
end_datetime: str,
run_manager: Optional[CallbackManagerForToolRun] = None,
) -> str:
# Get calendar object
schedule = self.account.schedule()
calendar = schedule.get_default_calendar()
event = calendar.new_event()
event.body = body
event.subject = subject
try:
event.start = dt.fromisoformat(start_datetime).replace(
tzinfo=ZoneInfo("UTC")
)
except ValueError:
# fallback for backwards compatibility
event.start = dt.strptime(start_datetime, UTC_FORMAT)
try:
event.end = dt.fromisoformat(end_datetime).replace(tzinfo=ZoneInfo("UTC"))
except ValueError:
# fallback for backwards compatibility
event.end = dt.strptime(end_datetime, UTC_FORMAT)
for attendee in attendees:
event.attendees.add(attendee)
event.save()
output = "Event sent: " + str(event)
return output

View File

@@ -0,0 +1,68 @@
from typing import List, Optional, Type
from langchain_core.callbacks import CallbackManagerForToolRun
from pydantic import BaseModel, Field
from langchain_community.tools.office365.base import O365BaseTool
class SendMessageSchema(BaseModel):
"""Input for SendMessageTool."""
body: str = Field(
...,
description="The message body to be sent.",
)
to: List[str] = Field(
...,
description="The list of recipients.",
)
subject: str = Field(
...,
description="The subject of the message.",
)
cc: Optional[List[str]] = Field(
None,
description="The list of CC recipients.",
)
bcc: Optional[List[str]] = Field(
None,
description="The list of BCC recipients.",
)
class O365SendMessage(O365BaseTool):
"""Send an email in Office 365."""
name: str = "send_email"
description: str = (
"Use this tool to send an email with the provided message fields."
)
args_schema: Type[SendMessageSchema] = SendMessageSchema
def _run(
self,
body: str,
to: List[str],
subject: str,
cc: Optional[List[str]] = None,
bcc: Optional[List[str]] = None,
run_manager: Optional[CallbackManagerForToolRun] = None,
) -> str:
# Get mailbox object
mailbox = self.account.mailbox()
message = mailbox.new_message()
# Assign message values
message.body = body
message.subject = subject
message.to.add(to)
if cc is not None:
message.cc.add(cc)
if bcc is not None:
message.bcc.add(bcc)
message.send()
output = "Message sent: " + str(message)
return output

View File

@@ -0,0 +1,79 @@
"""O365 tool utils."""
from __future__ import annotations
import logging
import os
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from O365 import Account
logger = logging.getLogger(__name__)
def clean_body(body: str) -> str:
"""Clean body of a message or event."""
try:
from bs4 import BeautifulSoup
try:
# Remove HTML
soup = BeautifulSoup(str(body), "html.parser")
body = soup.get_text()
# Remove return characters
body = "".join(body.splitlines())
# Remove extra spaces
body = " ".join(body.split())
return str(body)
except Exception:
return str(body)
except ImportError:
return str(body)
def authenticate() -> Account:
"""Authenticate using the Microsoft Graph API"""
try:
from O365 import Account
except ImportError as e:
raise ImportError(
"Cannot import 0365. Please install the package with `pip install O365`."
) from e
if "CLIENT_ID" in os.environ and "CLIENT_SECRET" in os.environ:
client_id = os.environ["CLIENT_ID"]
client_secret = os.environ["CLIENT_SECRET"]
credentials = (client_id, client_secret)
else:
logger.error(
"Error: The CLIENT_ID and CLIENT_SECRET environmental variables have not "
"been set. Visit the following link on how to acquire these authorization "
"tokens: https://learn.microsoft.com/en-us/graph/auth/"
)
return None
account = Account(credentials)
if account.is_authenticated is False:
if not account.authenticate(
scopes=[
"https://graph.microsoft.com/Mail.ReadWrite",
"https://graph.microsoft.com/Mail.Send",
"https://graph.microsoft.com/Calendars.ReadWrite",
"https://graph.microsoft.com/MailboxSettings.ReadWrite",
]
):
print("Error: Could not authenticate") # noqa: T201
return None
else:
return account
else:
return account
UTC_FORMAT = "%Y-%m-%dT%H:%M:%S%z"
"""UTC format for datetime objects."""