Skip to content

Commit 785b347

Browse files
committed
merge conflict
1 parent ad30df4 commit 785b347

10 files changed

Lines changed: 72 additions & 105 deletions

File tree

examples/hello_agent.py

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import os
22
from pathlib import Path
33

4-
from pydantic_ai.models.anthropic import AnthropicModel
4+
import httpx
5+
from pydantic_ai.models.openai import OpenAIModel
6+
from pydantic_ai.providers.openai import OpenAIProvider
57

68
import marvin
79

@@ -13,9 +15,16 @@ def write_file(path: str, content: str):
1315

1416

1517
writer = marvin.Agent(
16-
model=AnthropicModel(
17-
model_name="claude-3-5-sonnet-latest",
18-
api_key=os.getenv("ANTHROPIC_API_KEY"),
18+
model=OpenAIModel(
19+
"gpt-4o",
20+
provider=OpenAIProvider(
21+
api_key=os.getenv("OPENAI_API_KEY", "gonna fail"),
22+
base_url="https://api.openai.com/v1",
23+
http_client=httpx.AsyncClient(
24+
# proxy="http://localhost:8080",
25+
# headers={"x-SOME-HEADER": "some-value"},
26+
),
27+
),
1928
),
2029
name="Technical Writer",
2130
instructions="Write concise, engaging content for developers",

examples/pandas/dataframe.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,3 @@
1-
# /// script
2-
# dependencies = ["marvin@git+https://github.com/prefecthq/marvin.git", "pandas", "prefect"]
3-
# ///
4-
51
"""
62
fills out a new column in a dataframe, adding values to many columns concurrently
73
"""

examples/slackbot/core.py

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -185,11 +185,12 @@ def create_agent(
185185
logger = get_run_logger()
186186
logger.info("Creating new agent")
187187
ai_model = model or AnthropicModel(
188-
cast(
188+
provider="anthropic",
189+
api_key=Secret.load(settings.claude_key_secret_name, _sync=True).get(), # type: ignore
190+
model=cast(
189191
str,
190192
Variable.get("marvin_bot_model", default=settings.model_name, _sync=True), # type: ignore
191193
),
192-
api_key=Secret.load(settings.claude_key_secret_name, _sync=True).get(), # type: ignore
193194
)
194195
agent = Agent(
195196
model=ai_model,
@@ -216,27 +217,23 @@ def personality_and_maybe_notes(ctx: RunContext[UserContext]) -> str: # type: i
216217
if ctx.deps["user_notes"]
217218
else ""
218219
)
219-
logger.debug(f"System prompt: {system_prompt}")
220+
print(f"System prompt: {system_prompt}")
220221
return system_prompt
221222

222223
@agent.tool
223224
def store_facts_about_user(ctx: RunContext[UserContext], facts: list[str]) -> str: # type: ignore[reportUnusedFunction]
224-
logger = get_run_logger()
225-
logger.info(f"Storing {len(facts)} facts about user {ctx.deps['user_id']}")
225+
print(f"Storing {len(facts)} facts about user {ctx.deps['user_id']}")
226226
with TurboPuffer(
227227
namespace=f"{settings.user_facts_namespace_prefix}{ctx.deps['user_id']}"
228228
) as tpuf:
229229
tpuf.upsert(documents=[Document(text=fact) for fact in facts])
230230
message = f"Stored {len(facts)} facts about user {ctx.deps['user_id']}"
231-
logger.info(message)
231+
print(message)
232232
return message
233233

234234
@agent.tool
235235
def delete_facts_about_user(ctx: RunContext[UserContext], related_to: str) -> str: # type: ignore[reportUnusedFunction]
236-
logger = get_run_logger()
237-
logger.info(
238-
f"forgetting stuff about {ctx.deps['user_id']} related to {related_to}"
239-
)
236+
print(f"forgetting stuff about {ctx.deps['user_id']} related to {related_to}")
240237
user_id = ctx.deps["user_id"]
241238
with TurboPuffer(
242239
namespace=f"{settings.user_facts_namespace_prefix}{user_id}"
@@ -245,7 +242,7 @@ def delete_facts_about_user(ctx: RunContext[UserContext], related_to: str) -> st
245242
ids = [str(v.id) for v in vector_result.data or []]
246243
tpuf.delete(ids)
247244
message = f"Deleted {len(ids)} facts about user {user_id}"
248-
logger.info(message)
245+
print(message)
249246
return message
250247

251248
return agent

examples/slackbot/settings.py

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import os
21
from pathlib import Path
32
from typing import ClassVar, Literal
43

@@ -61,6 +60,8 @@ def validate_log_level(cls, v: str) -> str:
6160
default=False, description="Enable test mode with auto-reload"
6261
)
6362

63+
slack_api_token: str = Field(default=..., description="Slack API bot user token")
64+
6465
@property
6566
def model_name(self) -> str:
6667
return Variable.get(
@@ -69,16 +70,5 @@ def model_name(self) -> str:
6970
_sync=True, # type: ignore
7071
)
7172

72-
@property
73-
def slack_api_token(self) -> str:
74-
from prefect.blocks.system import Secret
75-
76-
if self.test_mode:
77-
return Secret.load("test-slack-api-token", _sync=True).get() # type: ignore
78-
else:
79-
token = os.getenv("MARVIN_SLACK_API_TOKEN")
80-
assert token is not None, "MARVIN_SLACK_API_TOKEN is not set"
81-
return token
82-
8373

8474
settings = SlackbotSettings()

examples/slackbot/slack.py

Lines changed: 24 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
11
"""Module for Slack-related utilities."""
22

3-
import os
43
import re
54
from typing import Any, List, Union
65

76
import httpx
87
from pydantic import BaseModel, ValidationInfo, field_validator, model_validator
9-
10-
import marvin
8+
from settings import settings
119

1210

1311
class EventBlockElement(BaseModel):
@@ -78,53 +76,25 @@ def validate_event(
7876
return v
7977

8078

81-
async def get_token() -> str:
82-
"""Get the Slack bot token from the environment."""
83-
try:
84-
token = (
85-
marvin.settings.slack_api_token
86-
) # set `MARVIN_SLACK_API_TOKEN` in `~/.marvin/.env
87-
except AttributeError:
88-
if token := os.getenv("MARVIN_SLACK_API_TOKEN"):
89-
return token
90-
try: # TODO: clean this up
91-
from prefect.blocks.system import Secret
92-
93-
return (await Secret.load("slack-api-token")).get()
94-
except ImportError:
95-
pass
96-
raise ValueError(
97-
"`MARVIN_SLACK_API_TOKEN` not found in environment."
98-
" Please set it in `~/.marvin/.env` or as an environment variable."
99-
)
100-
return token
101-
102-
103-
def convert_md_links_to_slack(text) -> str:
79+
def convert_md_links_to_slack(text: str) -> str:
10480
md_link_pattern = r"\[(?P<text>[^\]]+)]\((?P<url>[^\)]+)\)"
10581

10682
# converting Markdown links to Slack-style links
107-
def to_slack_link(match):
83+
def to_slack_link(match: re.Match[str]) -> str:
10884
return f"<{match.group('url')}|{match.group('text')}>"
10985

11086
# Replace Markdown links with Slack-style links
111-
slack_text = re.sub(md_link_pattern, to_slack_link, text)
112-
113-
slack_text = re.sub(r"\*\*(.*?)\*\*", r"*\1*", slack_text)
114-
115-
return slack_text
87+
return re.sub(
88+
r"\*\*(.*?)\*\*", r"*\1*", re.sub(md_link_pattern, to_slack_link, text)
89+
)
11690

11791

11892
async def post_slack_message(
11993
message: str,
12094
channel_id: str,
121-
attachments: Union[list[dict[str, Any]], None] = None,
122-
thread_ts: Union[str, None] = None,
123-
auth_token: Union[str, None] = None,
95+
attachments: list[dict[str, Any]] | None = None,
96+
thread_ts: str | None = None,
12497
) -> httpx.Response:
125-
if not auth_token:
126-
auth_token = await get_token()
127-
12898
post_data = {
12999
"channel": channel_id,
130100
"text": convert_md_links_to_slack(message),
@@ -137,7 +107,7 @@ async def post_slack_message(
137107
async with httpx.AsyncClient() as client:
138108
response = await client.post(
139109
"https://slack.com/api/chat.postMessage",
140-
headers={"Authorization": f"Bearer {auth_token}"},
110+
headers={"Authorization": f"Bearer {settings.slack_api_token}"},
141111
json=post_data,
142112
)
143113
response_data = response.json()
@@ -147,12 +117,14 @@ async def post_slack_message(
147117
return response
148118

149119

150-
async def get_thread_messages(channel: str, thread_ts: str) -> list:
120+
async def get_thread_messages(
121+
channel: str, thread_ts: str, auth_token: str
122+
) -> list[dict[str, Any]]:
151123
"""Get all messages from a slack thread."""
152124
async with httpx.AsyncClient() as client:
153125
response = await client.get(
154126
"https://slack.com/api/conversations.replies",
155-
headers={"Authorization": f"Bearer {await get_token()}"},
127+
headers={"Authorization": f"Bearer {auth_token}"},
156128
params={"channel": channel, "ts": thread_ts},
157129
)
158130
response.raise_for_status()
@@ -161,10 +133,11 @@ async def get_thread_messages(channel: str, thread_ts: str) -> list:
161133

162134
async def get_user_name(user_id: str) -> str:
163135
async with httpx.AsyncClient() as client:
136+
auth_token = settings.slack_api_token
164137
response = await client.get(
165138
"https://slack.com/api/users.info",
166139
params={"user": user_id},
167-
headers={"Authorization": f"Bearer {await get_token()}"}, # noqa: E501
140+
headers={"Authorization": f"Bearer {auth_token}"},
168141
)
169142
return (
170143
response.json().get("user", {}).get("name", user_id)
@@ -178,7 +151,7 @@ async def get_channel_name(channel_id: str) -> str:
178151
response = await client.get(
179152
"https://slack.com/api/conversations.info",
180153
params={"channel": channel_id},
181-
headers={"Authorization": f"Bearer {await get_token()}"}, # noqa: E501
154+
headers={"Authorization": f"Bearer {settings.slack_api_token}"},
182155
)
183156
return (
184157
response.json().get("channel", {}).get("name", channel_id)
@@ -193,7 +166,7 @@ async def fetch_current_message_text(channel: str, ts: str) -> str:
193166
response = await client.get(
194167
"https://slack.com/api/conversations.replies",
195168
params={"channel": channel, "ts": ts},
196-
headers={"Authorization": f"Bearer {await get_token()}"}, # noqa: E501
169+
headers={"Authorization": f"Bearer {settings.slack_api_token}"},
197170
)
198171
response.raise_for_status()
199172
messages = response.json().get("messages", [])
@@ -233,7 +206,7 @@ async def edit_slack_message(
233206
async with httpx.AsyncClient() as client:
234207
response = await client.post(
235208
"https://slack.com/api/chat.update",
236-
headers={"Authorization": f"Bearer {await get_token()}"},
209+
headers={"Authorization": f"Bearer {settings.slack_api_token}"},
237210
json={"channel": channel_id, "ts": thread_ts, "text": updated_text},
238211
)
239212

@@ -244,9 +217,8 @@ async def edit_slack_message(
244217
async def search_slack_messages(
245218
query: str,
246219
max_messages: int = 3,
247-
channel: Union[str, None] = None,
248-
user_auth_token: Union[str, None] = None,
249-
) -> list:
220+
channel: str | None = None,
221+
) -> list[dict[str, Any]]:
250222
"""
251223
Search for messages in Slack workspace based on a query.
252224
@@ -259,12 +231,9 @@ async def search_slack_messages(
259231
Returns:
260232
list: A list of message contents and permalinks matching the query.
261233
"""
262-
all_messages = []
234+
all_messages: list[dict[str, Any]] = []
263235
next_cursor = None
264236

265-
if not user_auth_token:
266-
user_auth_token = await get_token()
267-
268237
async with httpx.AsyncClient() as client:
269238
while len(all_messages) < max_messages:
270239
params = {
@@ -278,7 +247,7 @@ async def search_slack_messages(
278247

279248
response = await client.get(
280249
"https://slack.com/api/search.messages",
281-
headers={"Authorization": f"Bearer {user_auth_token}"},
250+
headers={"Authorization": f"Bearer {settings.slack_api_token}"},
282251
params=params,
283252
)
284253

@@ -302,14 +271,11 @@ async def search_slack_messages(
302271
return all_messages[:max_messages]
303272

304273

305-
async def get_workspace_info(slack_bot_token: Union[str, None] = None) -> dict:
306-
if not slack_bot_token:
307-
slack_bot_token = await get_token()
308-
274+
async def get_workspace_info() -> dict[str, Any]:
309275
async with httpx.AsyncClient() as client:
310276
response = await client.get(
311277
"https://slack.com/api/team.info",
312-
headers={"Authorization": f"Bearer {slack_bot_token}"},
278+
headers={"Authorization": f"Bearer {settings.slack_api_token}"},
313279
)
314280
response.raise_for_status()
315281
return response.json().get("team", {})

examples/slackbot/start.py

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,7 @@
11
if __name__ == "__main__":
2-
import os
3-
42
import uvicorn
5-
from prefect.blocks.system import Secret
63
from settings import settings
74

8-
os.environ["OPENAI_API_KEY"] = Secret.load("openai-api-key", _sync=True).get() # type: ignore
9-
os.environ["MARVIN_SLACK_API_TOKEN"] = settings.slack_api_token
10-
115
uvicorn.run(
126
"api:app",
137
host=settings.host,

examples/timeseries.py

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -53,41 +53,44 @@ def build_context(thread: ThreadViewPost) -> dict[str, Any]:
5353
if thread and thread.post:
5454
context["bsky post"] = {
5555
"author": thread.post.author.handle,
56-
"text": thread.post.record.text,
56+
"text": getattr(thread.post.record, "text", ""),
5757
}
5858

5959
if hasattr(thread.post.record, "embed") and hasattr(
6060
thread.post.embed, "images"
6161
):
62+
assert thread.post.embed and (
63+
img := getattr(thread.post.embed, "images", [])[0]
64+
)
6265
image_description_result = visual_extraction_agent.run(
6366
[
6467
"summarize this image concisely, include direct quotes from the image",
65-
ImageUrl(url=thread.post.embed.images[0].fullsize),
68+
ImageUrl(url=img.fullsize),
6669
]
6770
)
6871
context["bsky post"]["embed"] = image_description_result
6972

7073
if hasattr(thread, "replies"):
7174
context["replies"] = [
7275
{
73-
"author": reply.post.author.handle,
74-
"text": reply.post.record.text,
76+
"author": reply_post.author.handle,
77+
"text": reply_post.record.text,
7578
**(
7679
{
7780
"embed": visual_extraction_agent.run(
7881
"summarize this image concisely, include direct quotes from the image",
7982
attachments=[
80-
ImageUrl(url=reply.post.embed.images[0].fullsize),
83+
ImageUrl(url=reply_post.embed.images[0].fullsize),
8184
],
8285
)
8386
}
84-
if hasattr(reply.post.record, "embed")
85-
and hasattr(reply.post.embed, "images")
87+
if hasattr(reply_post.record, "embed")
88+
and hasattr(reply_post.embed, "images")
8689
else {}
8790
),
8891
}
8992
for reply in thread.replies or []
90-
if hasattr(reply, "post")
93+
if (reply_post := getattr(reply, "post", None))
9194
]
9295

9396
return context
@@ -138,7 +141,7 @@ def explain_bsky_post(
138141

139142
details = {
140143
"facts": [
141-
"@<username> on blue sky will tag someone in a post",
144+
"@<username> on bluesky will tag someone in a post",
142145
"a post embed is an image that goes with a post",
143146
"jlowin.dev | jeremiah is Prefect's CEO, who is the original poster",
144147
"zzstoatzz | alternatebuild.dev | nate is an engineer at Prefect",

0 commit comments

Comments
 (0)