Skip to content
Open
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
38 changes: 30 additions & 8 deletions tools/python/examples/openai/customer_support/emailer.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ def to_message(self, reply_id: str, reply_to: str) -> MIMEMultipart:
msg["In-Reply-To"] = reply_id
msg["References"] = reply_id
msg["Reply-To"] = reply_to
msg["Bcc"] = self.from_address
msg.attach(MIMEText(f"<html><body>{self.body}</body></html>", "html"))
return msg

Expand All @@ -58,7 +59,6 @@ class Emailer:
"""
Emailer is an IMAP/SMTP client that can be used to fetch and respond to emails.
It was mostly vibe-coded so please make improvements!
TODO: add agent replies to the context
"""

def __init__(
Expand All @@ -73,7 +73,9 @@ def __init__(
):
# Email configuration
self.email_address = email_address
self.support_address = support_address if support_address else email_address
self.support_address = (
support_address if support_address else email_address
)
self.email_password = email_password
self.imap_server = imap_server
self.imap_port = imap_port
Expand Down Expand Up @@ -177,7 +179,8 @@ def _get_email_thread(
_, thread_ids = imap_conn.search(None, f"X-GM-THRID {thread_id}")
if thread_ids and thread_ids[0]:
thread = [
self._parse_email(imap_conn, mid) for mid in thread_ids[0].split()
self._parse_email(imap_conn, mid)
for mid in thread_ids[0].split()
]
thread = [e for e in thread if e]
thread.sort(key=lambda e: e.date)
Expand All @@ -189,15 +192,21 @@ def _get_email_thread(
)
if ref_data and ref_data[0]:
ref_line = (
ref_data[0][1].decode() if isinstance(ref_data[0][1], bytes) else ""
ref_data[0][1].decode()
if isinstance(ref_data[0][1], bytes)
else ""
)
refs = re.findall(r"<([^>]+)>", ref_line)
for ref in refs:
_, ref_ids = imap_conn.search(None, f'(HEADER Message-ID "<{ref}>")')
_, ref_ids = imap_conn.search(
None, f'(HEADER Message-ID "<{ref}>")'
)
if ref_ids and ref_ids[0]:
for ref_id in ref_ids[0].split():
ref_email = self._parse_email(imap_conn, ref_id)
if ref_email and ref_email.id not in [e.id for e in thread]:
if ref_email and ref_email.id not in [
e.id for e in thread
]:
thread.append(ref_email)

# Sort emails in the thread by date (ascending order)
Expand All @@ -206,13 +215,26 @@ def _get_email_thread(

return thread

def _get_unread_emails(self, imap_conn: imaplib.IMAP4_SSL) -> List[List[Email]]:
def _get_unread_emails(
self, imap_conn: imaplib.IMAP4_SSL
) -> List[List[Email]]:
imap_conn.select("INBOX")
_, msg_nums = imap_conn.search(None, f'(UNSEEN TO "{self.support_address}")')
_, msg_nums = imap_conn.search(
None, f'(UNSEEN TO "{self.support_address}")'
)
emails: List[List[Email]] = []

for email_id in msg_nums[0].split():
thread = self._get_email_thread(imap_conn, email_id)
if not thread:
continue

most_recent = thread[-1]
if most_recent.from_address == self.support_address:
# Agent's own reply (e.g. BCC'd), mark as read so it stops showing up as unseen
self.mark_as_read(imap_conn, email_id.decode())
continue

emails.append(thread)

return emails
Expand Down
11 changes: 8 additions & 3 deletions tools/python/examples/openai/file_search/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from pydantic import BaseModel, Field

from dotenv import load_dotenv

load_dotenv()

from agents import Agent, Runner
Expand All @@ -14,8 +15,12 @@
class InvoiceOutput(BaseModel):
name: str = Field(description="The name of the customer")
email: str = Field(description="The email of the customer")
service: str = Field(description="The service that the customer is invoiced for")
amount_due: int = Field(description="The dollar amount due for the invoice. Convert text to dollar amounts if needed.")
service: str = Field(
description="The service that the customer is invoiced for"
)
amount_due: int = Field(
description="The dollar amount due for the invoice. Convert text to dollar amounts if needed."
)
id: str = Field(description="The id of the stripe invoice")


Expand Down Expand Up @@ -46,7 +51,7 @@ async def main():
)
],
output_type=InvoiceListOutput,
handoffs=[invoice_agent]
handoffs=[invoice_agent],
)

assignment = "Search for all customers that haven't paid across all of my documents. Handoff to the invoice agent to create, finalize, and send an invoice for each."
Expand Down
1 change: 1 addition & 0 deletions tools/python/examples/openai/web_search/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import os

from dotenv import load_dotenv

load_dotenv()

from agents import Agent, Runner, WebSearchTool
Expand Down
4 changes: 1 addition & 3 deletions tools/python/examples/strands/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,7 @@ async def main():
tools = stripe_agent_toolkit.get_tools()

# Create agent with Stripe tools
agent = Agent(
tools=tools
)
agent = Agent(tools=tools)

# Test the agent
response = agent("""
Expand Down
2 changes: 2 additions & 0 deletions tools/python/stripe_agent_toolkit/configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@

class Context(TypedDict, total=False):
"""Context for MCP connection."""

account: Optional[str]
customer: Optional[str]
mode: Optional[str]


class Configuration(TypedDict, total=False):
"""Configuration for Stripe Agent Toolkit."""

context: Optional[Context]
38 changes: 16 additions & 22 deletions tools/python/stripe_agent_toolkit/crewai/toolkit.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,14 @@ def _run(self, **kwargs: Any) -> str:
if loop.is_running():
# If we're already in an async context, create a new loop
import concurrent.futures

with concurrent.futures.ThreadPoolExecutor() as pool:
future = pool.submit(
asyncio.run,
self.run_tool(self.method, kwargs)
asyncio.run, self.run_tool(self.method, kwargs)
)
return future.result()
else:
return loop.run_until_complete(
self.run_tool(self.method, kwargs)
)
return loop.run_until_complete(self.run_tool(self.method, kwargs))

async def _arun(self, **kwargs: Any) -> str:
"""Async execution via MCP."""
Expand All @@ -57,36 +55,33 @@ class StripeAgentToolkit(ToolkitCore[List[StripeTool]]):
"""

def __init__(
self,
secret_key: str,
configuration: Optional[Configuration] = None
self, secret_key: str, configuration: Optional[Configuration] = None
):
super().__init__(secret_key, configuration)

def _empty_tools(self) -> List[StripeTool]:
"""Return empty list of tools."""
return []

def _convert_tools(
self,
mcp_tools: List[McpTool]
) -> List[StripeTool]:
def _convert_tools(self, mcp_tools: List[McpTool]) -> List[StripeTool]:
"""Convert MCP tools to CrewAI StripeTool instances."""
tools = []
for mcp_tool in mcp_tools:
# Convert JSON Schema to Pydantic model
args_schema = json_schema_to_pydantic_model(
mcp_tool.get("inputSchema"),
model_name=f"{mcp_tool['name']}_args"
model_name=f"{mcp_tool['name']}_args",
)

tools.append(StripeTool(
run_tool=self.run_tool,
method=mcp_tool["name"],
name=mcp_tool["name"],
description=mcp_tool.get("description", mcp_tool["name"]),
args_schema=args_schema,
))
tools.append(
StripeTool(
run_tool=self.run_tool,
method=mcp_tool["name"],
name=mcp_tool["name"],
description=mcp_tool.get("description", mcp_tool["name"]),
args_schema=args_schema,
)
)
return tools

@property
Expand All @@ -101,8 +96,7 @@ def tools(self) -> List[StripeTool]:


async def create_stripe_agent_toolkit(
secret_key: str,
configuration: Optional[Configuration] = None
secret_key: str, configuration: Optional[Configuration] = None
) -> StripeAgentToolkit:
"""
Factory function to create and initialize a StripeAgentToolkit.
Expand Down
38 changes: 16 additions & 22 deletions tools/python/stripe_agent_toolkit/langchain/toolkit.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,14 @@ def _run(self, **kwargs: Any) -> str:
if loop.is_running():
# If we're already in an async context, create a new loop
import concurrent.futures

with concurrent.futures.ThreadPoolExecutor() as pool:
future = pool.submit(
asyncio.run,
self.run_tool(self.method, kwargs)
asyncio.run, self.run_tool(self.method, kwargs)
)
return future.result()
else:
return loop.run_until_complete(
self.run_tool(self.method, kwargs)
)
return loop.run_until_complete(self.run_tool(self.method, kwargs))

async def _arun(self, **kwargs: Any) -> str:
"""Async execution via MCP."""
Expand All @@ -57,36 +55,33 @@ class StripeAgentToolkit(ToolkitCore[List[StripeTool]]):
"""

def __init__(
self,
secret_key: str,
configuration: Optional[Configuration] = None
self, secret_key: str, configuration: Optional[Configuration] = None
):
super().__init__(secret_key, configuration)

def _empty_tools(self) -> List[StripeTool]:
"""Return empty list of tools."""
return []

def _convert_tools(
self,
mcp_tools: List[McpTool]
) -> List[StripeTool]:
def _convert_tools(self, mcp_tools: List[McpTool]) -> List[StripeTool]:
"""Convert MCP tools to LangChain StripeTool instances."""
tools = []
for mcp_tool in mcp_tools:
# Convert JSON Schema to Pydantic model
args_schema = json_schema_to_pydantic_model(
mcp_tool.get("inputSchema"),
model_name=f"{mcp_tool['name']}_args"
model_name=f"{mcp_tool['name']}_args",
)

tools.append(StripeTool(
run_tool=self.run_tool,
method=mcp_tool["name"],
name=mcp_tool["name"],
description=mcp_tool.get("description", mcp_tool["name"]),
args_schema=args_schema,
))
tools.append(
StripeTool(
run_tool=self.run_tool,
method=mcp_tool["name"],
name=mcp_tool["name"],
description=mcp_tool.get("description", mcp_tool["name"]),
args_schema=args_schema,
)
)
return tools

@property
Expand All @@ -101,8 +96,7 @@ def tools(self) -> List[StripeTool]:


async def create_stripe_agent_toolkit(
secret_key: str,
configuration: Optional[Configuration] = None
secret_key: str, configuration: Optional[Configuration] = None
) -> StripeAgentToolkit:
"""
Factory function to create and initialize a StripeAgentToolkit.
Expand Down
17 changes: 5 additions & 12 deletions tools/python/stripe_agent_toolkit/openai/toolkit.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,20 +25,15 @@ class StripeAgentToolkit(ToolkitCore[List[FunctionTool]]):
"""

def __init__(
self,
secret_key: str,
configuration: Optional[Configuration] = None
self, secret_key: str, configuration: Optional[Configuration] = None
):
super().__init__(secret_key, configuration)

def _empty_tools(self) -> List[FunctionTool]:
"""Return empty list of tools."""
return []

def _convert_tools(
self,
mcp_tools: List[McpTool]
) -> List[FunctionTool]:
def _convert_tools(self, mcp_tools: List[McpTool]) -> List[FunctionTool]:
"""Convert MCP tools to OpenAI FunctionTool instances."""
tools = []
for mcp_tool in mcp_tools:
Expand All @@ -51,8 +46,7 @@ def _create_function_tool(self, mcp_tool: McpTool) -> FunctionTool:
tool_name = mcp_tool["name"]

async def on_invoke_tool(
ctx: RunContextWrapper[Any],
input_str: str
ctx: RunContextWrapper[Any], input_str: str
) -> str:
args = json.loads(input_str)
return await toolkit.run_tool(tool_name, args)
Expand All @@ -77,7 +71,7 @@ async def on_invoke_tool(
description=mcp_tool.get("description", tool_name),
params_json_schema=parameters,
on_invoke_tool=on_invoke_tool,
strict_json_schema=False
strict_json_schema=False,
)

@property
Expand All @@ -92,8 +86,7 @@ def tools(self) -> List[FunctionTool]:


async def create_stripe_agent_toolkit(
secret_key: str,
configuration: Optional[Configuration] = None
secret_key: str, configuration: Optional[Configuration] = None
) -> StripeAgentToolkit:
"""
Factory function to create and initialize a StripeAgentToolkit.
Expand Down
5 changes: 4 additions & 1 deletion tools/python/stripe_agent_toolkit/shared/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@
from .constants import VERSION, MCP_SERVER_URL, TOOLKIT_HEADER, MCP_HEADER
from .async_initializer import AsyncInitializer
from .mcp_client import StripeMcpClient, McpTool, McpToolInputSchema
from .schema_utils import json_schema_to_pydantic_model, json_schema_to_pydantic_fields
from .schema_utils import (
json_schema_to_pydantic_model,
json_schema_to_pydantic_fields,
)
from .toolkit_core import ToolkitCore

__all__ = [
Expand Down
Loading
Loading