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
44 changes: 44 additions & 0 deletions python-langgraph/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# LangGraph: Build Stateful AI Agents in Python

This folder contains the source code for [LangGraph: Build Stateful AI Agents in Python](https://realpython.com/langgraph-build-stateful-ai-agents-in-python/)

## Setup

Create a new virtual environment, and run the following command to install LangGraph and the additional requirements for this project:

```console
(venv) $ python -m pip install -r requirements.txt
```

You'll use `langchain-openai` to interact with OpenAI LLMs, but keep in mind you can use any LLM provider you like with LangGraph and LangChain. You'll use [`pydantic`](https://realpython.com/python-pydantic/) to validate the information your agent parses from emails.

Before moving forward, if you choose to use OpenAI, make sure you're signed up for an OpenAI account and you have a valid [API key](https://openai.com/api/). You'll need to set the following [environment variable](https://en.wikipedia.org/wiki/Environment_variable) before running any examples in this tutorial:

```dotenv
OPENAI_API_KEY=<YOUR-OPENAI-API-KEY>
```

## Usage

Once your environment is set up, you can run the final graph agent on an example input with the following code:

```python
from graphs.email_agent import email_agent_graph
from example_emails import EMAILS

escalation_criteria = """"There's an immediate risk of electrical,
water, or fire damage"""

message_with_criteria = f"""
The escalation criteria is: {escalation_criteria}

Here's the email:
{EMAILS[3]}
"""
message_3 = {"messages": [("human", message_with_criteria)]}

for chunk in email_agent_graph.stream(message_3, stream_mode="values"):
chunk["messages"][-1].pretty_print()
```

See the tutorial for all the details on what's going on here.
31 changes: 31 additions & 0 deletions python-langgraph/chains/binary_questions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
from pydantic import BaseModel, Field


class BinaryAnswer(BaseModel):
is_true: bool = Field(
description="""Whether the answer to the question is yes or no.
True if yes otherwise false."""
)


binary_question_prompt = ChatPromptTemplate.from_messages(
[
(
"system",
"""
Answer this question as True for "yes" and False for "no".
No other answers are allowed:
{question}
""",
)
]
)

binary_question_model = ChatOpenAI(model="gpt-4o-mini", temperature=0)

BINARY_QUESTION_CHAIN = (
binary_question_prompt
| binary_question_model.with_structured_output(BinaryAnswer)
)
33 changes: 33 additions & 0 deletions python-langgraph/chains/escalation_check.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
from pydantic import BaseModel, Field


class EscalationCheck(BaseModel):
needs_escalation: bool = Field(
description="""Whether the notice requires escalation according
to specified criteria"""
)


escalation_prompt = ChatPromptTemplate.from_messages(
[
(
"system",
"""
Determine whether the following notice received from a regulatory
body requires immediate escalation. Immediate escalation is
required when {escalation_criteria}.
Here's the notice message:
{message}
""",
)
]
)

escalation_check_model = ChatOpenAI(model="gpt-4o-mini", temperature=0)

ESCALATION_CHECK_CHAIN = (
escalation_prompt
| escalation_check_model.with_structured_output(EscalationCheck)
)
106 changes: 106 additions & 0 deletions python-langgraph/chains/notice_extraction.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
from datetime import date, datetime

from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
from pydantic import BaseModel, Field, computed_field


class NoticeEmailExtract(BaseModel):
date_of_notice_str: str | None = Field(
default=None,
exclude=True,
repr=False,
description="""The date of the notice (if any) reformatted
to match YYYY-mm-dd""",
)
entity_name: str | None = Field(
default=None,
description="""The name of the entity sending the notice (if present
in the message)""",
)
entity_phone: str | None = Field(
default=None,
description="""The phone number of the entity sending the notice
(if present in the message)""",
)
entity_email: str | None = Field(
default=None,
description="""The email of the entity sending the notice
(if present in the message)""",
)
project_id: int | None = Field(
default=None,
description="""The project ID (if present in the message) -
must be an integer""",
)
site_location: str | None = Field(
default=None,
description="""The site location of the project (if present
in the message). Use the full address if possible.""",
)
violation_type: str | None = Field(
default=None,
description="""The type of violation (if present in the
message)""",
)
required_changes: str | None = Field(
default=None,
description="""The required changes specified by the entity
(if present in the message)""",
)
compliance_deadline_str: str | None = Field(
default=None,
exclude=True,
repr=False,
description="""The date that the company must comply (if any)
reformatted to match YYYY-mm-dd""",
)
max_potential_fine: float | None = Field(
default=None,
description="""The maximum potential fine
(if any)""",
)

@staticmethod
def _convert_string_to_date(date: str | None) -> date | None:
try:
return datetime.strptime(date, "%Y-%m-%d").date()
except Exception as e:
print(e)
return None

@computed_field
@property
def date_of_notice(self) -> date | None:
return self._convert_string_to_date(self.date_of_notice_str)

@computed_field
@property
def compliance_deadline(self) -> date | None:
return self._convert_string_to_date(self.compliance_deadline_str)


info_parse_prompt = ChatPromptTemplate.from_messages(
[
(
"system",
"""
Parse the date of notice, sending entity name, sending entity
phone, sending entity email, project id, site location, violation
type, required changes, compliance deadline, and maximum potential
fine from the message. If any of the fields aren't present, don't
populate them. Try to cast dates into the YYYY-mm-dd format. Don't
populate fields if they're not present in the message.
Here's the notice message:
{message}
""",
)
]
)

notice_parser_model = ChatOpenAI(model="gpt-4o-mini", temperature=0)

NOTICE_PARSER_CHAIN = (
info_parse_prompt
| notice_parser_model.with_structured_output(NoticeEmailExtract)
)
72 changes: 72 additions & 0 deletions python-langgraph/example_emails.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
EMAILS = [
# Email 1
"""
Date: October 15, 2024
From: Occupational Safety and Health Administration (OSHA)
To: Blue Ridge Construction, project 111232345 - Downtown Office Complex
Location: Dallas, TX

During a recent inspection of your construction site at 123 Main Street,
the following safety violations were identified:

Lack of fall protection: Workers on scaffolding above 10 feet were
without required harnesses or other fall protection equipment.
Unsafe scaffolding setup: Several scaffolding structures were noted as
lacking secure base plates and bracing, creating potential collapse risks.
Inadequate personal protective equipment (PPE): Multiple workers were found
without proper PPE, including hard hats and safety glasses.
Required Corrective Actions:

Install guardrails and fall arrest systems on all scaffolding over 10 feet.
Conduct an inspection of all scaffolding structures and reinforce unstable
sections. Ensure all workers on-site are provided with necessary PPE and conduct
safety training on proper usage.
Deadline for Compliance: All violations must be rectified by November 10, 2024.
Failure to comply may result in fines of up to $25,000 per violation.

Contact: For questions or to confirm compliance, please reach out to the OSHA
regional office at (555) 123-4567 or email [email protected].
""",
# Email 2
"""
From: [email protected]
Hey Betsy,
Here's your invoice for $1000 for the cookies you ordered.
""",
# Email 3
"""
From: [email protected]
Hi Paul,
We have an issue with the HVAC system your team installed in
apartment 1235. We'd like to request maintenance or a refund.
Thanks,
Terrance
""",
# Email 4
"""
Date: January 10, 2025
From: City of Los Angeles Building and Safety Department
To: West Coast Development, project 345678123 - Sunset Luxury
Condominiums
Location: Los Angeles, CA
Following an inspection of your site at 456 Sunset Boulevard, we have
identified the following building code violations:
Electrical Wiring: Exposed wiring was found in the underground parking
garage, posing a safety hazard. Fire Safety: Insufficient fire
extinguishers were available across multiple floors of the structure
under construction.
Structural Integrity: The temporary support beams in the eastern wing
do not meet the load-bearing standards specified in local building codes.
Required Corrective Actions:
Replace or properly secure exposed wiring to meet electrical safety
standards. Install additional fire extinguishers in compliance with
fire code requirements. Reinforce or replace temporary support beams
to ensure structural stability. Deadline for Compliance: Violations
must be addressed no later than February 5,
2025. Failure to comply may result in
a stop-work order and additional fines.
Contact: For questions or to schedule a re-inspection, please contact
the Building and Safety Department at
(555) 456-7890 or email [email protected].
""",
]
Loading