Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
2 changes: 1 addition & 1 deletion examples/slackbot/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ An intelligent Slack bot built with Marvin that provides AI-powered assistance i
uv sync

# Start the bot server
uv run examples/slackbot/start.py
uv run -m examples.slackbot

# Or with Docker
docker build -f examples/slackbot/Dockerfile.slackbot -t marvin-slackbot .
Expand Down
6 changes: 3 additions & 3 deletions examples/slackbot/Dockerfile.slackbot
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM python:3.12-slim
FROM python:3.13-slim

COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/

Expand All @@ -11,8 +11,8 @@ RUN apt-get update && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*

RUN uv sync --extra slackbot
RUN uv sync --extra slackbot --no-dev

EXPOSE 4200

CMD ["uv", "run", "examples/slackbot/start.py"]
CMD ["uv", "run", "--no-sync", "-m", "slackbot"]
2 changes: 1 addition & 1 deletion examples/slackbot/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ A Slack chatbot powered by Claude with memories and Prefect-specific knowledge.
├── api.py # FastAPI app and Slack event handlers
├── core.py # Database, agent, and memory management
├── settings.py # Configuration management
└── start.py # Entry point
└── __main__.py # Entry point
```

## Setup
Expand Down
63 changes: 63 additions & 0 deletions examples/slackbot/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
[build-system]
requires = ["hatchling", "uv-dynamic-versioning>=0.7.0"]
build-backend = "hatchling.build"

[tool.hatch.version]
source = "uv-dynamic-versioning"

[tool.uv-dynamic-versioning]
vcs = "git"
style = "pep440"
bump = true

[project]
name = "slackbot"
dynamic = ["version", "dependencies"]
description = "Slackbot for Prefect"
authors = [{ name = "Nathan Nowack", email = "nate@prefect.io" }]
license = "MIT"
readme = "README.md"
classifiers = [
"Development Status :: 4 - Beta",
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Intended Audience :: Developers",
"Intended Audience :: Information Technology",
"Intended Audience :: System Administrators",
"License :: OSI Approved :: MIT License",
"Operating System :: Unix",
"Operating System :: POSIX :: Linux",
"Environment :: Console",
"Environment :: MacOS X",
"Topic :: Software Development :: Libraries :: Python Modules",
"Topic :: Internet",
]
requires-python = ">=3.9"

[tool.hatch.metadata.hooks.uv-dynamic-versioning]
dependencies = [
"marvin",
"prefect",
"numpy",
"raggy[tpuf]@git+https://github.com/zzstoatzz/raggy.git",
]


[dependency-groups]
dev = []

[tool.hatch.metadata]
allow-direct-references = true


[tool.hatch.build.targets.wheel]
packages = ["src/slackbot"]

[tool.uv.sources]
marvin = { workspace = true }
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,16 @@

if __name__ == "__main__":
import uvicorn
from settings import settings

from slackbot.settings import settings

if not (openai_api_key := os.getenv("OPENAI_API_KEY")):
os.environ["OPENAI_API_KEY"] = Secret.load(
settings.openai_api_key_secret_name, _sync=True
).get()

uvicorn.run(
"api:app",
"slackbot.api:app",
host=settings.host,
port=settings.port,
# reload=settings.test_mode,
Expand Down
31 changes: 16 additions & 15 deletions examples/slackbot/api.py → examples/slackbot/src/slackbot/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,6 @@
from contextlib import asynccontextmanager
from typing import Any

from core import (
Database,
UserContext,
build_user_context,
create_agent,
)
from fastapi import FastAPI, HTTPException, Request
from prefect import flow, get_run_logger, task
from prefect.blocks.notifications import SlackWebhook
Expand All @@ -18,10 +12,17 @@
from prefect.variables import Variable
from pydantic_ai.agent import AgentRunResult
from pydantic_ai.messages import ModelMessage
from settings import settings
from slack import SlackPayload, get_channel_name, post_slack_message
from strings import count_tokens, slice_tokens
from wrap import WatchToolCalls

from slackbot.core import (
Database,
UserContext,
build_user_context,
create_agent,
)
from slackbot.settings import settings
from slackbot.slack import SlackPayload, get_channel_name, post_slack_message
from slackbot.strings import count_tokens, slice_tokens
from slackbot.wrap import WatchToolCalls

BOT_MENTION = r"<@(\w+)>"

Expand Down Expand Up @@ -120,11 +121,11 @@ async def lifespan(app: FastAPI):
@app.post("/chat")
async def chat_endpoint(request: Request) -> dict[str, Any]:
try:
payload = SlackPayload(**await request.json())
payload = SlackPayload.model_validate(await request.json())
except Exception as e:
logger.error(f"Error parsing Slack payload: {e}")
slack_webhook = await SlackWebhook.load("marvin-bot-pager") # type: ignore
await slack_webhook.notify( # type: ignore
slack_webhook = await SlackWebhook.load("marvin-bot-pager")
await slack_webhook.notify(
body=f"Error parsing Slack payload: {e}",
subject="Slackbot Error",
)
Expand Down Expand Up @@ -155,8 +156,8 @@ async def chat_endpoint(request: Request) -> dict[str, Any]:
channel_name = await get_channel_name(payload.event.channel)
if channel_name.startswith("D"):
logger.warning(f"Attempted DM in channel: {channel_name}")
slack_webhook = await SlackWebhook.load("marvin-bot-pager") # type: ignore
await slack_webhook.notify( # type: ignore
slack_webhook = await SlackWebhook.load("marvin-bot-pager")
await slack_webhook.notify(
body=f"Attempted DM: {channel_name}",
subject="Slackbot DM Warning",
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from contextlib import asynccontextmanager
from dataclasses import dataclass
from pathlib import Path
from typing import AsyncIterator, TypedDict, cast
from typing import AsyncIterator, TypedDict

from prefect import get_run_logger, task
from prefect.blocks.system import Secret
Expand All @@ -17,11 +17,12 @@
from pydantic_ai.settings import ModelSettings
from raggy.documents import Document
from raggy.vectorstores.tpuf import TurboPuffer, query_namespace
from research_agent import research_prefect_topic
from search import read_github_issues
from settings import settings
from turbopuffer.error import NotFoundError

from slackbot.research_agent import research_prefect_topic
from slackbot.search import read_github_issues
from slackbot.settings import settings

GITHUB_API_TOKEN = Secret.load(settings.github_token_secret_name, _sync=True).get() # type: ignore

logger = get_logger(__name__)
Expand Down Expand Up @@ -155,12 +156,11 @@ def create_agent(
logger = get_run_logger()
logger.info("Creating new agent")
ai_model = model or AnthropicModel(
model_name=Variable.get(
"marvin_bot_model", default=settings.model_name, _sync=True
),
provider="anthropic",
api_key=Secret.load(settings.claude_key_secret_name, _sync=True).get(), # type: ignore
model=cast(
str,
Variable.get("marvin_bot_model", default=settings.model_name, _sync=True), # type: ignore
),
)
agent = Agent[UserContext, str](
model=ai_model,
Expand Down
File renamed without changes.
Empty file.
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
"""Research agent for improved information gathering."""

from modules import display_signature
from prefect import task
from prefect.cache_policies import INPUTS
from pydantic import BaseModel, Field
from pydantic_ai import Agent
from pydantic_ai.models import Model
from search import (

from slackbot.modules import display_signature
from slackbot.search import (
explore_module_offerings,
get_latest_prefect_release_notes,
review_common_3x_gotchas,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,16 @@

import httpx
import turbopuffer as tpuf
from modules import ModuleTreeExplorer
from prefect import task
from prefect.blocks.system import Secret
from pydantic import BaseModel, Field, field_validator
from raggy.vectorstores.tpuf import multi_query_tpuf
from settings import settings
from strings import slice_tokens

import marvin
from marvin.utilities.logging import get_logger
from slackbot.modules import ModuleTreeExplorer
from slackbot.settings import settings
from slackbot.strings import slice_tokens


def verify_import_statements(import_statements: list[str]) -> str:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@

import httpx
from pydantic import BaseModel, ValidationInfo, field_validator, model_validator
from settings import settings

from slackbot.settings import settings


class EventBlockElement(BaseModel):
Expand Down
File renamed without changes.
File renamed without changes.
2 changes: 1 addition & 1 deletion justfile
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,4 @@ run-pre-commits: check-uv
uv run pre-commit run --all-files

run-slackbot: check-uv
uv run --extra slackbot examples/slackbot/start.py
uv run --extra slackbot -m slackbot
13 changes: 7 additions & 6 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,7 @@ dev = [
[project.optional-dependencies]
audio = ["pyaudio>=0.2.14"]

slackbot = [
"pydantic-ai",
"prefect",
"numpy",
"raggy[tpuf]@git+https://github.com/zzstoatzz/raggy.git",
]
slackbot = ["slackbot"]

mcp = ["fastmcp"]

Expand Down Expand Up @@ -101,6 +96,12 @@ extend-select = ["I"]
[tool.ruff.lint.per-file-ignores]
"__init__.py" = ["F401", "I001", "RUF013"]

[tool.uv.sources]
slackbot = { workspace = true }

[tool.uv.workspace]
members = ["examples/slackbot"]

[build-system]
requires = ["hatchling>=1.21.0", "hatch-vcs>=0.4.0"]
build-backend = "hatchling.build"
Loading