-
Notifications
You must be signed in to change notification settings - Fork 0
/
web_scraping.py
166 lines (131 loc) · 4.06 KB
/
web_scraping.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
"""
This example shows how to use tools with Anthropic models.
Download `uv` to run this example: https://github.com/astral-sh/uv
```
export ANTHROPIC_API_KEY="..."
uv run examples/tool_use.py
```
"""
# /// script
# dependencies = [
# "anthropic",
# "hype @ git+https://github.com/mattt/hype.git",
# "duckduckgo-search",
# "beautifulsoup4",
# "pydantic",
# "httpx",
# "pint",
# ]
# ///
import datetime
from textwrap import dedent
from typing import Literal
import anthropic
import httpx
import pint
from bs4 import BeautifulSoup
from duckduckgo_search import DDGS
from pydantic import BaseModel
import hype
ureg = pint.UnitRegistry()
class Recipe(BaseModel):
"""
A recipe with its name, prep time, cook time, ingredients, and steps.
"""
name: str
"""The name of the recipe."""
prep_time: datetime.timedelta
"""The time it takes to prepare the recipe."""
cook_time: datetime.timedelta
"""The time it takes to cook the recipe."""
ingredients: list[str]
"""The ingredients in the recipe."""
steps: list[str]
"""A list of steps for preparing the recipe."""
@hype.up
def web_search(query: str, num_results: int = 5) -> list[dict]:
"""
Perform a web search using DuckDuckGo.
:param query: The search query.
:param num_results: Number of results to return (default: 5).
:return: A list of dictionaries containing search results.
"""
with DDGS() as ddgs:
return list(ddgs.text(query, max_results=num_results))
@hype.up
def scrape_webpage(url: str) -> str:
"""
Scrape the content of a webpage.
:param url: The URL of the webpage to scrape.
:return: The text content of the webpage.
"""
with httpx.Client() as client: # pylint: disable=redefined-outer-name
response = client.get(url, timeout=30) # pylint: disable=redefined-outer-name
soup = BeautifulSoup(response.content, "html.parser")
return soup.get_text(strip=True)
# fmt:off
Unit = Literal[
"g", "kg", "mg", "µg",
"l", "ml", "µl",
"cup", "tsp", "tbsp",
"floz", "pt", "qt", "gal",
"oz", "lb",
"°C", "°F"
]
# fmt:on
@hype.up
def convert_quantity(value: float, from_unit: Unit, to_unit: Unit) -> tuple[float, str]:
"""
Convert quantity from one unit to another.
:param value: The numeric value to convert.
:param from_unit: The unit to convert from.
:param to_unit: The unit to convert to.
:return: A tuple with the converted value and unit.
"""
quantity = value * ureg(from_unit)
converted = quantity.to(to_unit)
return converted.magnitude, str(converted.units)
if __name__ == "__main__":
client = anthropic.Anthropic()
tools = hype.create_anthropic_tools(
[web_search, scrape_webpage, convert_quantity],
result_type=Recipe,
)
messages: list[anthropic.types.MessageParam] = [
{
"role": "user",
"content": dedent(
"""
Find a recipe for avocado and black bean tacos,
convert units to metric,
and translate instructions into Italian.
""".strip(),
),
}
]
for message in messages:
print(message["content"])
while not tools.future.done():
response = client.messages.create(
model="claude-3-5-sonnet-20240620",
max_tokens=2046,
messages=messages,
tools=tools,
)
for block in response.content:
print(block)
if response.stop_reason == "tool_use":
messages.append({"role": "assistant", "content": response.content})
for block in response.content:
if block.type == "tool_use":
result = tools(block)
print("Result:", result)
messages.append(
{
"role": "user",
"content": [result],
}
)
else:
break
print(f"Final result: {tools.future.result()}")