Skip to content

Commit c1f1af8

Browse files
committed
add pydantic demos, improve weather demo
1 parent f6ae9c9 commit c1f1af8

File tree

4 files changed

+86
-81
lines changed

4 files changed

+86
-81
lines changed

logfire-hello-world/main.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import logfire
22

3-
logfire.configure(environment='hello-world')
3+
logfire.configure(service_name='hello-world')
44

55
logfire.info('hello {place}', place='world')

pai-pydantic/retry.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
from datetime import date
2+
3+
import logfire
4+
from pydantic import BaseModel, field_validator
5+
from pydantic_ai import Agent
6+
7+
logfire.configure(service_name='pai-pydantic-retry')
8+
logfire.instrument_pydantic_ai()
9+
10+
11+
class Person(BaseModel):
12+
"""Definition of an historic person"""
13+
14+
name: str
15+
dob: date
16+
city: str
17+
18+
@field_validator('dob')
19+
def validate_dob(cls, v: date) -> date:
20+
if v >= date(1900, 1, 1):
21+
raise ValueError('The person must be born in the 19th century')
22+
return v
23+
24+
25+
agent = Agent(
26+
'openai:gpt-4o',
27+
output_type=Person,
28+
instructions='Extract information about the person',
29+
)
30+
result = agent.run_sync("Samuel lived in London and was born on Jan 28th '87")
31+
print(repr(result.output))

pai-pydantic/simple.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
from datetime import date
2+
3+
import logfire
4+
from pydantic import BaseModel
5+
from pydantic_ai import Agent
6+
7+
logfire.configure(service_name='pai-pydantic-simple')
8+
logfire.instrument_pydantic_ai()
9+
10+
11+
class Person(BaseModel):
12+
name: str
13+
dob: date
14+
city: str
15+
16+
17+
agent = Agent(
18+
'openai:gpt-4o',
19+
output_type=Person,
20+
instructions='Extract information about the person',
21+
)
22+
result = agent.run_sync("Samuel lived in London and was born on Jan 28th '87")
23+
print(repr(result.output))

pai-weather/main.py

Lines changed: 31 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -1,65 +1,53 @@
11
from __future__ import annotations as _annotations
22

33
import asyncio
4-
import os
5-
import urllib.parse
64
from dataclasses import dataclass
5+
from random import randint
76
from typing import Any
87

98
import logfire
109
from httpx import AsyncClient
11-
from pydantic_ai import Agent, ModelRetry, RunContext
10+
from pydantic import BaseModel
11+
from pydantic_ai import Agent, RunContext
1212

13-
# 'if-token-present' means nothing will be sent (and the example will work) if you don't have logfire configured
14-
logfire.configure()
13+
logfire.configure(service_name='pai-weather')
1514
logfire.instrument_pydantic_ai()
16-
logfire.instrument_httpx(capture_all=True)
1715

1816

1917
@dataclass
2018
class Deps:
2119
client: AsyncClient
22-
weather_api_key: str | None
23-
geo_api_key: str | None
2420

2521

2622
weather_agent = Agent(
27-
'openai:gpt-4o',
23+
'openai:gpt-4.1-mini',
2824
# 'Be concise, reply with one sentence.' is enough for some models (like openai) to use
2925
# the below tools appropriately, but others like anthropic and gemini require a bit more direction.
30-
instructions=(
31-
'Be concise, reply with one sentence.'
32-
'Use the `get_lat_lng` tool to get the latitude and longitude of the locations, '
33-
'then use the `get_weather` tool to get the weather.'
34-
),
26+
instructions='Be concise, reply with one sentence.',
3527
deps_type=Deps,
3628
retries=2,
3729
)
3830

3931

32+
class LatLng(BaseModel):
33+
lat: float
34+
lng: float
35+
36+
4037
@weather_agent.tool
41-
async def get_lat_lng(ctx: RunContext[Deps], location_description: str) -> dict[str, float]:
38+
async def get_lat_lng(ctx: RunContext[Deps], location_description: str) -> LatLng:
4239
"""Get the latitude and longitude of a location.
4340
4441
Args:
4542
ctx: The context.
4643
location_description: A description of a location.
4744
"""
48-
if ctx.deps.geo_api_key is None:
49-
# if no API key is provided, return a dummy response (London)
50-
return {'lat': 51.1, 'lng': -0.1}
51-
52-
params = {'access_token': ctx.deps.geo_api_key}
53-
loc = urllib.parse.quote(location_description)
54-
r = await ctx.deps.client.get(f'https://api.mapbox.com/geocoding/v5/mapbox.places/{loc}.json', params=params)
45+
r = await ctx.deps.client.get(
46+
'https://demo-endpoints.pydantic.workers.dev/latlng',
47+
params={'location': location_description, 'sleep': randint(200, 1200)},
48+
)
5549
r.raise_for_status()
56-
data = r.json()
57-
58-
if features := data['features']:
59-
lat, lng = features[0]['center']
60-
return {'lat': lat, 'lng': lng}
61-
else:
62-
raise ModelRetry('Could not find the location')
50+
return LatLng.model_validate_json(r.content)
6351

6452

6553
@weather_agent.tool
@@ -71,62 +59,25 @@ async def get_weather(ctx: RunContext[Deps], lat: float, lng: float) -> dict[str
7159
lat: Latitude of the location.
7260
lng: Longitude of the location.
7361
"""
74-
if ctx.deps.weather_api_key is None:
75-
# if no API key is provided, return a dummy response
76-
return {'temperature': '21 °C', 'description': 'Sunny'}
77-
78-
params = {
79-
'apikey': ctx.deps.weather_api_key,
80-
'location': f'{lat},{lng}',
81-
'units': 'metric',
82-
}
83-
with logfire.span('calling weather API', params=params) as span:
84-
r = await ctx.deps.client.get('https://api.tomorrow.io/v4/weather/realtime', params=params)
85-
r.raise_for_status()
86-
data = r.json()
87-
span.set_attribute('response', data)
88-
89-
values = data['data']['values']
90-
# https://docs.tomorrow.io/reference/data-layers-weather-codes
91-
code_lookup = {
92-
1000: 'Clear, Sunny',
93-
1100: 'Mostly Clear',
94-
1101: 'Partly Cloudy',
95-
1102: 'Mostly Cloudy',
96-
1001: 'Cloudy',
97-
2000: 'Fog',
98-
2100: 'Light Fog',
99-
4000: 'Drizzle',
100-
4001: 'Rain',
101-
4200: 'Light Rain',
102-
4201: 'Heavy Rain',
103-
5000: 'Snow',
104-
5001: 'Flurries',
105-
5100: 'Light Snow',
106-
5101: 'Heavy Snow',
107-
6000: 'Freezing Drizzle',
108-
6001: 'Freezing Rain',
109-
6200: 'Light Freezing Rain',
110-
6201: 'Heavy Freezing Rain',
111-
7000: 'Ice Pellets',
112-
7101: 'Heavy Ice Pellets',
113-
7102: 'Light Ice Pellets',
114-
8000: 'Thunderstorm',
115-
}
116-
return {
117-
'temperature': f'{values["temperatureApparent"]:0.0f}°C',
118-
'description': code_lookup.get(values['weatherCode'], 'Unknown'),
119-
}
62+
temp_response, descr_response = await asyncio.gather(
63+
ctx.deps.client.get(
64+
'https://demo-endpoints.pydantic.workers.dev/number',
65+
params={'min': 10, 'max': 30, 'sleep': randint(200, 1200)},
66+
),
67+
ctx.deps.client.get(
68+
'https://demo-endpoints.pydantic.workers.dev/weather',
69+
params={'lat': lat, 'lng': lng, 'sleep': randint(200, 1200)},
70+
),
71+
)
72+
temp_response.raise_for_status()
73+
descr_response.raise_for_status()
74+
return {'temperature': f'{temp_response.text} °C', 'description': descr_response.text}
12075

12176

12277
async def main():
12378
async with AsyncClient() as client:
12479
logfire.instrument_httpx(client, capture_all=True)
125-
# create a free API key at https://www.tomorrow.io/weather-api/
126-
weather_api_key = os.getenv('WEATHER_API_KEY')
127-
# create a free API key at https://www.mapbox.com/
128-
geo_api_key = os.getenv('GEO_API_KEY')
129-
deps = Deps(client=client, weather_api_key=weather_api_key, geo_api_key=geo_api_key)
80+
deps = Deps(client=client)
13081
result = await weather_agent.run('What is the weather like in London and in Wiltshire?', deps=deps)
13182
print('Response:', result.output)
13283

0 commit comments

Comments
 (0)