This commit is contained in:
Lance Martin
2025-01-15 10:39:17 -08:00
commit 09d388c031
9 changed files with 295 additions and 0 deletions
+2
View File
@@ -0,0 +1,2 @@
OPENAI_API_KEY=sk-xxx
SLACK_WEBHOOK=xxx
+47
View File
@@ -0,0 +1,47 @@
# 🤖 o1 Daily Brew ☕
o1 Daily Brew is a daily reflection from o1 published to a Slack channel of your choice.
## Quick Start
1. Clone the repo.
2. Create a Slack app and add your webhook URL to your environment variable `SLACK_WEBHOOK`(see below).
3. Create a [LangGraph Platform deployment](https://langchain-ai.github.io/langgraph/concepts/deployment_options/).
4. Create a [cron job](https://langchain-ai.github.io/langgraph/cloud/how-tos/cron_jobs/) to run the deployment at your desired time.
```python
from langgraph_sdk import get_client
# URL from our LangGraph Cloud deployment
url = "deployment_url"
client = get_client(url=url)
# An assistant ID is automatically created for each deployment
await client.assistants.search(metadata={"created_by": "system"})
# Use the SDK to schedule a cron job to run at 11:00 AM PST (19:00 UTC) every day
cron_job_stateless = await client.crons.create(
your_assistant_id,
schedule="0 19 * * *",
input={"user_provided_topics": "AI"}
)
```
## Slack
Create a Slack app to publish to Slack.
1. Go to https://api.slack.com/apps
2. Click "Create New App"
3. Choose "From scratch"
4. Name your app (e.g., "o1 Daily Brew") and select your workspace
5. Once created, go to "Incoming Webhooks" in the left sidebar
6. Toggle "Activate Incoming Webhooks" to On
7. Click "Add New Webhook to Workspace"
8. Choose the channel where you want the messages to appear
9. Copy the "Webhook URL" that's generated
Add add webhook URL credentials to your environment variable `SLACK_WEBHOOK`.
+11
View File
@@ -0,0 +1,11 @@
{
"dockerfile_lines": [],
"graphs": {
"o1_daily_brew": "./src/chatbot/graph.py:graph"
},
"python_version": "3.11",
"env": "./.env",
"dependencies": [
"."
]
}
+55
View File
@@ -0,0 +1,55 @@
[project]
name = "o1-daily-brew"
version = "0.0.1"
description = "Daily reflection from o1."
authors = [
{ name = "Lance Martin" }
]
readme = "README.md"
license = { text = "MIT" }
requires-python = ">=3.9"
dependencies = [
"langgraph>=0.2.55",
"langchain-community>=0.3.9",
"langchain-openai>=0.3.0",
]
[project.optional-dependencies]
dev = ["mypy>=1.11.1", "ruff>=0.6.1"]
[build-system]
requires = ["setuptools>=73.0.0", "wheel"]
build-backend = "setuptools.build_meta"
[tool.setuptools]
packages = ["daily_brew"]
[tool.setuptools.package-dir]
"chatbot" = "src/daily_brew"
[tool.setuptools.package-data]
"*" = ["py.typed"]
[tool.ruff]
lint.select = [
"E", # pycodestyle
"F", # pyflakes
"I", # isort
"D", # pydocstyle
"D401", # First line should be in imperative mood
"T201",
"UP",
]
lint.ignore = [
"UP006",
"UP007",
"UP035",
"D417",
"E501",
]
[tool.ruff.lint.per-file-ignores]
"tests/*" = ["D", "UP"]
[tool.ruff.lint.pydocstyle]
convention = "google"
View File
+28
View File
@@ -0,0 +1,28 @@
import os
from dataclasses import dataclass, fields
from typing import Any, Optional
from langchain_core.runnables import RunnableConfig
from daily_brew import prompts
@dataclass(kw_only=True)
class Configuration:
"""The configurable fields for o1 daily brew."""
model: str = "o1"
DAILY_BREW_PROMPT: str = prompts.DAILY_BREW_PROMPT
@classmethod
def from_runnable_config(
cls, config: Optional[RunnableConfig] = None
) -> "Configuration":
"""Create a Configuration instance from a RunnableConfig."""
configurable = (
config["configurable"] if config and "configurable" in config else {}
)
values: dict[str, Any] = {
f.name: os.environ.get(f.name.upper(), configurable.get(f.name))
for f in fields(cls)
if f.init
}
return cls(**{k: v for k, v in values.items() if v})
+111
View File
@@ -0,0 +1,111 @@
import os
import requests
from datetime import datetime
from langchain_core.runnables import RunnableConfig
from langchain_openai import ChatOpenAI
from langgraph.graph import START, END, StateGraph
import daily_brew.configuration as configuration
from daily_brew.state import State, Brew
def make_brew(state: State, config: RunnableConfig):
"""Generate the daily brew
Args:
state (State): State
config (RunnableConfig): Configuration object
Returns:
dict: Updated state with the daily brew
"""
# Get the configuration
configurable = configuration.Configuration.from_runnable_config(config)
model = configurable.model
DAILY_BREW_PROMPT = configurable.DAILY_BREW_PROMPT
# Format prompt and run model
instructions = DAILY_BREW_PROMPT.format(time=datetime.now().isoformat())
llm = ChatOpenAI(model=model).with_structured_output(Brew)
brew = llm.invoke(instructions)
# Update state with the daily brew
return {"brew": brew}
def write_to_slack(state: State):
"""Generate the daily brew
Args:
state (State): State
Returns:
None
"""
# Full set of interview reports
brew = state.brew
# Write to your Slack Channel via webhook
true = True
headers = {
'Content-Type': 'application/json',
}
# Blocks
blocks = []
# Block 1: Title section
blocks.append({
"type": "section",
"text": {
"type": "mrkdwn",
"text": f"*{brew.title}*"
}
})
# Block 2: Divider
blocks.append({
"type": "divider"
})
# Block 3: Content section
blocks.append({
"type": "section",
"text": {
"type": "mrkdwn",
"text": f"{brew.brew}"
}
})
# Block 4: Divider
blocks.append({
"type": "divider"
})
blocks.insert(0, {
"type": "header",
"text": {
"type": "plain_text",
"text": ":coffee: o1 is brewing ...",
"emoji": true
}
})
data = {
"blocks": blocks,
"unfurl_links": True,
"unfurl_media": True,
}
response = requests.post(os.getenv("SLACK_WEBHOOK"), headers=headers, json=data)
# Create the graph + all nodes
builder = StateGraph(State, config_schema=configuration.Configuration)
builder.add_node("make_brew",make_brew)
builder.add_node("write_to_slack",write_to_slack)
builder.add_edge(START, "make_brew")
builder.add_edge("make_brew", "write_to_slack")
builder.add_edge("write_to_slack", END)
# Compile the graph
graph = builder.compile()
+24
View File
@@ -0,0 +1,24 @@
DAILY_BREW_PROMPT = """I want you to generate a morning reflection for me.
---
Format the reflection or "brew" as a structured output with a fun title and content.
---
Consider the current date and time: {time}
Share an engaging historical reflection or story connected to this date or season.
You might consider:
- Notable events that occurred on this day in history
- How historical figures experienced this time of year
- Daily routines or traditions of remarkable people during this season
- Cultural or historical significance of this particular time
- Interesting parallels between past and present
Ground your reflection in specific historical details and biographical accounts.
Make it personal and vivid, as if we're stepping into a moment in time.
Be concise but engaging, aiming to spark curiosity and connection to the past."""
+17
View File
@@ -0,0 +1,17 @@
import operator
from dataclasses import dataclass, field
from pydantic import BaseModel, Field
from typing_extensions import List, Annotated
class Brew(BaseModel):
title: str = Field(
description="Punchy summary title for the daily brew",
)
brew: str = Field(
description="Content of the daily brew",
)
@dataclass(kw_only=True)
class State:
brew: Brew = field(default=None)