pixie-examples 0.1.1.dev3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,246 @@
1
+ # PydanticAI Examples with Pixie SDK
2
+
3
+ This directory contains examples from the [PydanticAI Catalog](https://github.com/pydantic/pydantic-ai) integrated with Pixie SDK for observability and execution.
4
+
5
+ ## Examples from Catalog
6
+
7
+ All examples are sourced from the official PydanticAI catalog and adapted for Pixie SDK.
8
+
9
+ ### 1. pydantic_model.py - Pydantic Model (Quickstart)
10
+
11
+ Simple example demonstrating structured output extraction from text.
12
+
13
+ **Pixie Handler:** `pydantic_model_example`
14
+ **Documentation:** [pydantic-model](https://ai.pydantic.dev/examples/pydantic-model/)
15
+ **Source:** [pydantic_model.py](https://github.com/pydantic/pydantic-ai/blob/main/examples/pydantic_ai_examples/pydantic_model.py)
16
+
17
+ - **Pattern:** Async function
18
+ - **Input:** String query (e.g., "The windy city in the US of A.")
19
+ - **Output:** Structured `MyModel` with city and country
20
+ - **Features:**
21
+ - Basic agent setup with structured output
22
+ - Pydantic model validation
23
+ - Simple query → structured response
24
+
25
+ ### 2. bank_support.py - Bank Support (Multi-Turn Chatbot)
26
+
27
+ Interactive bank support agent with tools and conversation history.
28
+
29
+ **Pixie Handler:** `bank_support_agent`
30
+ **Documentation:** [bank-support](https://ai.pydantic.dev/examples/bank-support/)
31
+ **Source:** [bank_support.py](https://github.com/pydantic/pydantic-ai/blob/main/examples/pydantic_ai_examples/bank_support.py)
32
+
33
+ - **Pattern:** Async generator (multi-turn)
34
+ - **Input:** None (interactive)
35
+ - **Output:** Generator[str, str] for conversation
36
+ - **Features:**
37
+ - Multi-turn conversation flow
38
+ - Tool usage (customer_balance)
39
+ - Dynamic system instructions
40
+ - SQLite database integration
41
+ - Interactive user input with `UserInputRequirement`
42
+
43
+ ### 3. flight_booking.py - Flight Booking (Multi-Agent)
44
+
45
+ Multi-agent system for flight search, extraction, and seat selection.
46
+
47
+ **Pixie Handler:** `flight_booking_example`
48
+ **Documentation:** [flight-booking](https://ai.pydantic.dev/examples/flight-booking/)
49
+ **Source:** [flight_booking.py](https://github.com/pydantic/pydantic-ai/blob/main/examples/pydantic_ai_examples/flight_booking.py)
50
+
51
+ - **Pattern:** Async generator (interactive)
52
+ - **Input:** None (interactive)
53
+ - **Output:** Generator[str, str] for booking flow
54
+ - **Features:**
55
+ - **Agent Delegation:** search_agent delegates to extraction_agent
56
+ - **Programmatic Hand-off:** flight search → seat selection
57
+ - Multi-agent collaboration
58
+ - Usage limits and tracking
59
+ - Output validation with constraints
60
+ - Interactive confirmation workflow
61
+
62
+ ### 4. question_graph.py - Question Graph (Graph/State-Machine)
63
+
64
+ Graph-based Q&A system using pydantic_graph for state machine control flow.
65
+
66
+ **Pixie Handler:** `question_graph_example`
67
+ **Documentation:** [question-graph](https://ai.pydantic.dev/examples/question-graph/)
68
+ **Source:** [question_graph.py](https://github.com/pydantic/pydantic-ai/blob/main/examples/pydantic_ai_examples/question_graph.py)
69
+
70
+ - **Pattern:** Async generator (interactive with graph)
71
+ - **Input:** None (interactive)
72
+ - **Output:** Generator[str, str] for Q&A flow
73
+ - **Features:**
74
+ - **pydantic_graph** for state machine control flow
75
+ - Graph nodes: Ask → Answer → Evaluate → Reprimand/End
76
+ - State management across nodes
77
+ - Conditional branching (correct/incorrect)
78
+ - Message history per agent
79
+
80
+ **Graph Flow:**
81
+
82
+ ```
83
+ Ask (generate question)
84
+
85
+ Answer (get user input)
86
+
87
+ Evaluate (check answer)
88
+
89
+ ├─ Correct → End (success)
90
+ └─ Wrong → Reprimand → Ask (retry)
91
+ ```
92
+
93
+ ### 5. sql_gen.py - SQL Generation (Multi-Step Workflow)
94
+
95
+ SQL query generation with validation and structured output.
96
+
97
+ **Pixie Handler:** `sql_gen_example`
98
+ **Documentation:** [sql-gen](https://ai.pydantic.dev/examples/sql-gen/)
99
+ **Source:** [sql_gen.py](https://github.com/pydantic/pydantic-ai/blob/main/examples/pydantic_ai_examples/sql_gen.py)
100
+
101
+ - **Pattern:** Async function
102
+ - **Input:** String query (natural language)
103
+ - **Output:** String (SQL query or error message)
104
+ - **Features:**
105
+ - Dynamic system prompts with SQL examples
106
+ - Output validation with `ModelRetry`
107
+ - Structured output with union types (`Success | InvalidRequest`)
108
+ - Multi-step validation workflow
109
+ - SQL safety checks (prevents dangerous operations)
110
+ - Schema-aware query generation
111
+
112
+ ## Setup
113
+
114
+ ### 1. Install Dependencies
115
+
116
+ From the project root:
117
+
118
+ ```bash
119
+ poetry install
120
+ ```
121
+
122
+ ### 2. Configure Environment Variables
123
+
124
+ Copy `.env.example` to `.env` and add your API keys:
125
+
126
+ ```bash
127
+ cp examples/pydantic_ai/.env.example examples/pydantic_ai/.env
128
+ ```
129
+
130
+ Edit `.env` and add:
131
+
132
+ ```bash
133
+ # Required for most examples
134
+ OPENAI_API_KEY=sk-...
135
+
136
+ # Optional
137
+ LOGFIRE_TOKEN=... # For observability
138
+ ```
139
+
140
+ ### 3. Start Pixie Server
141
+
142
+ ```bash
143
+ poetry run pixie
144
+ ```
145
+
146
+ The server will start at http://127.0.0.1:8000
147
+
148
+ ## Running Examples
149
+
150
+ ### Via GraphQL Playground
151
+
152
+ Open http://127.0.0.1:8000/graphql and run subscriptions:
153
+
154
+ #### Example 1: Pydantic Model
155
+
156
+ ```graphql
157
+ subscription {
158
+ run(name: "pydantic_model_example", inputData: "The capital of France") {
159
+ runId
160
+ status
161
+ data
162
+ }
163
+ }
164
+ ```
165
+
166
+ #### Example 2: Bank Support (Interactive)
167
+
168
+ ```graphql
169
+ subscription {
170
+ run(name: "bank_support_agent") {
171
+ runId
172
+ status
173
+ data
174
+ }
175
+ }
176
+ ```
177
+
178
+ #### Example 3: Flight Booking
179
+
180
+ ```graphql
181
+ subscription {
182
+ run(name: "flight_booking_example") {
183
+ runId
184
+ status
185
+ data
186
+ }
187
+ }
188
+ ```
189
+
190
+ #### Example 4: Question Graph
191
+
192
+ ```graphql
193
+ subscription {
194
+ run(name: "question_graph_example") {
195
+ runId
196
+ status
197
+ data
198
+ }
199
+ }
200
+ ```
201
+
202
+ #### Example 5: SQL Generation
203
+
204
+ ```graphql
205
+ subscription {
206
+ run(name: "sql_gen_example", inputData: "show me error logs from yesterday") {
207
+ runId
208
+ status
209
+ data
210
+ }
211
+ }
212
+ ```
213
+
214
+ ## Architecture Patterns
215
+
216
+ ### Async Functions vs Generators
217
+
218
+ - **Async Function** (`async def → Type`): Single request-response (pydantic_model, sql_gen)
219
+ - **Async Generator** (`async def → PixieGenerator[YieldType, SendType]`): Multi-turn (bank_support, flight_booking, question_graph)
220
+
221
+ ### Agent Patterns
222
+
223
+ 1. **Single Agent** - One agent handles workflow
224
+ 2. **Multi-Turn Chat** - Agent + user interaction loop
225
+ 3. **Agent Delegation** - One agent calls another
226
+ 4. **Programmatic Hand-off** - Sequential agent execution
227
+ 5. **Graph-Based Control** - pydantic_graph state machines
228
+
229
+ ## Integration with Pixie SDK
230
+
231
+ All examples use:
232
+
233
+ ```python
234
+ from pixie import app, PixieGenerator, UserInputRequirement
235
+
236
+ @app
237
+ async def my_handler(input: InputType) -> OutputType:
238
+ Agent.instrument_all() # Enable observability
239
+ # ... implementation
240
+ ```
241
+
242
+ ## Resources
243
+
244
+ - [PydanticAI Documentation](https://ai.pydantic.dev/)
245
+ - [Pixie SDK Guide](../../.github/copilot-instructions.md)
246
+ - [PydanticAI GitHub](https://github.com/pydantic/pydantic-ai)
File without changes
@@ -0,0 +1,154 @@
1
+ """Bank support agent example using Pydantic AI.
2
+
3
+ This example demonstrates:
4
+ - Dynamic system prompts with agent instructions
5
+ - Structured output types
6
+ - Tools for querying dependencies
7
+ - Integration with Pixie SDK
8
+
9
+ Run with:
10
+ poetry run pixie
11
+
12
+ Then query via GraphQL:
13
+ subscription {
14
+ run(name: "bank_support_agent", inputData: "What is my balance?") {
15
+ runId
16
+ status
17
+ data
18
+ }
19
+ }
20
+ """
21
+
22
+ import sqlite3
23
+ from dataclasses import dataclass
24
+ from pydantic import BaseModel
25
+ from pydantic_ai import Agent, RunContext
26
+ import pixie
27
+
28
+
29
+ @dataclass
30
+ class DatabaseConn:
31
+ """A wrapper over the SQLite connection."""
32
+
33
+ sqlite_conn: sqlite3.Connection
34
+
35
+ async def customer_name(self, *, customer_id: int) -> str | None:
36
+ res = self.sqlite_conn.execute(
37
+ "SELECT name FROM customers WHERE id=?", (customer_id,)
38
+ )
39
+ row = res.fetchone()
40
+ if row:
41
+ return row[0]
42
+ return None
43
+
44
+ async def customer_balance(self, *, customer_id: int) -> float:
45
+ res = self.sqlite_conn.execute(
46
+ "SELECT balance FROM customers WHERE id=?", (customer_id,)
47
+ )
48
+ row = res.fetchone()
49
+ if row:
50
+ return row[0]
51
+ else:
52
+ raise ValueError("Customer not found")
53
+
54
+
55
+ @dataclass
56
+ class SupportDependencies:
57
+ customer_id: int
58
+ db: DatabaseConn
59
+
60
+
61
+ class SupportOutput(BaseModel):
62
+ """Output model for bank support agent."""
63
+
64
+ support_advice: str
65
+ """Advice returned to the customer"""
66
+ block_card: bool
67
+ """Whether to block their card or not"""
68
+ risk: int
69
+ """Risk level of query"""
70
+
71
+
72
+ # Create the support agent
73
+ support_agent = Agent(
74
+ "openai:gpt-4o-mini",
75
+ deps_type=SupportDependencies,
76
+ output_type=SupportOutput,
77
+ instructions=(
78
+ "You are a support agent in our bank, give the "
79
+ "customer support and judge the risk level of their query. "
80
+ "Reply using the customer's name."
81
+ ),
82
+ )
83
+
84
+
85
+ @support_agent.instructions
86
+ async def add_customer_name(ctx: RunContext[SupportDependencies]) -> str:
87
+ customer_name = await ctx.deps.db.customer_name(customer_id=ctx.deps.customer_id)
88
+ return f"The customer's name is {customer_name!r}"
89
+
90
+
91
+ @support_agent.tool
92
+ async def customer_balance_tool(ctx: RunContext[SupportDependencies]) -> str:
93
+ """Returns the customer's current account balance."""
94
+ balance = await ctx.deps.db.customer_balance(
95
+ customer_id=ctx.deps.customer_id,
96
+ )
97
+ return f"${balance:.2f}"
98
+
99
+
100
+ @pixie.app
101
+ async def pydantic_ai_bank_support_agent() -> pixie.PixieGenerator[str, str]:
102
+ """Interactive bank support agent.
103
+
104
+ This agent helps customers with banking queries, can check balances,
105
+ and assesses risk levels of queries.
106
+
107
+ Yields:
108
+ str: Agent responses and support advice
109
+
110
+ Receives:
111
+ str: User queries via InputRequired
112
+ """
113
+
114
+ # Initialize database connection (in-memory for demo)
115
+ with sqlite3.connect(":memory:") as con:
116
+ cur = con.cursor()
117
+ cur.execute("CREATE TABLE customers(id, name, balance)")
118
+ cur.execute(
119
+ """
120
+ INSERT INTO customers VALUES
121
+ (123, 'John', 123.45),
122
+ (456, 'Jane', 567.89)
123
+ """
124
+ )
125
+ con.commit()
126
+
127
+ # Default customer
128
+ customer_id = 123
129
+ deps = SupportDependencies(
130
+ customer_id=customer_id, db=DatabaseConn(sqlite_conn=con)
131
+ )
132
+
133
+ yield "Welcome to Bank Support! I'm here to help you with your account."
134
+
135
+ while True:
136
+ # Get user query
137
+ user_query = yield pixie.InputRequired(str)
138
+
139
+ # Check for exit commands
140
+ if user_query.lower() in {"exit", "quit", "bye"}:
141
+ yield "Thank you for using our support service. Have a great day!"
142
+ break
143
+
144
+ # Run the agent
145
+ result = await support_agent.run(user_query, deps=deps)
146
+
147
+ # Format the response
148
+ output = result.output
149
+ response = f"""
150
+ Support Advice: {output.support_advice}
151
+ Card Status: {"🔒 BLOCKED" if output.block_card else "✓ Active"}
152
+ Risk Level: {output.risk}/10
153
+ """
154
+ yield response.strip()
@@ -0,0 +1,250 @@
1
+ """Example of a multi-agent flow where one agent delegates work to another.
2
+
3
+ This demonstrates agent delegation and programmatic agent hand-off patterns.
4
+ """
5
+
6
+ import datetime
7
+ from dataclasses import dataclass
8
+ from typing import Literal
9
+
10
+ from pydantic import BaseModel, Field
11
+ from pydantic_ai import (
12
+ Agent,
13
+ ModelMessage,
14
+ ModelRetry,
15
+ RunContext,
16
+ RunUsage,
17
+ UsageLimits,
18
+ )
19
+ import pixie
20
+
21
+
22
+ class FlightDetails(BaseModel):
23
+ """Details of the most suitable flight."""
24
+
25
+ flight_number: str
26
+ price: int
27
+ origin: str = Field(description="Three-letter airport code")
28
+ destination: str = Field(description="Three-letter airport code")
29
+ date: datetime.date
30
+
31
+
32
+ class NoFlightFound(BaseModel):
33
+ """When no valid flight is found."""
34
+
35
+
36
+ @dataclass
37
+ class Deps:
38
+ web_page_text: str
39
+ req_origin: str
40
+ req_destination: str
41
+ req_date: datetime.date
42
+
43
+
44
+ # This agent is responsible for controlling the flow of the conversation.
45
+ search_agent = Agent[Deps, FlightDetails | NoFlightFound](
46
+ "openai:gpt-4o-mini",
47
+ output_type=FlightDetails | NoFlightFound, # type: ignore
48
+ retries=4,
49
+ system_prompt=(
50
+ "Your job is to find the cheapest flight for the user on the given date. "
51
+ ),
52
+ )
53
+
54
+
55
+ # This agent is responsible for extracting flight details from web page text.
56
+ extraction_agent = Agent(
57
+ "openai:gpt-4o-mini",
58
+ output_type=list[FlightDetails],
59
+ system_prompt="Extract all the flight details from the given text.",
60
+ )
61
+
62
+
63
+ @search_agent.tool
64
+ async def extract_flights(ctx: RunContext[Deps]) -> list[FlightDetails]:
65
+ """Get details of all flights."""
66
+ # we pass the usage to the search agent so requests within this agent are counted
67
+ agent_result = await extraction_agent.run(ctx.deps.web_page_text, usage=ctx.usage)
68
+ return agent_result.output
69
+
70
+
71
+ @search_agent.output_validator
72
+ async def validate_output(
73
+ ctx: RunContext[Deps], output: FlightDetails | NoFlightFound
74
+ ) -> FlightDetails | NoFlightFound:
75
+ """Procedural validation that the flight meets the constraints."""
76
+ if isinstance(output, NoFlightFound):
77
+ return output
78
+
79
+ errors: list[str] = []
80
+ if output.origin != ctx.deps.req_origin:
81
+ errors.append(
82
+ f"Flight should have origin {ctx.deps.req_origin}, not {output.origin}"
83
+ )
84
+ if output.destination != ctx.deps.req_destination:
85
+ errors.append(
86
+ f"Flight should have destination {ctx.deps.req_destination}, not {output.destination}"
87
+ )
88
+ if output.date != ctx.deps.req_date:
89
+ errors.append(f"Flight should be on {ctx.deps.req_date}, not {output.date}")
90
+
91
+ if errors:
92
+ raise ModelRetry("\n".join(errors))
93
+ else:
94
+ return output
95
+
96
+
97
+ class SeatPreference(BaseModel):
98
+ row: int = Field(ge=1, le=30)
99
+ seat: Literal["A", "B", "C", "D", "E", "F"]
100
+
101
+
102
+ class Failed(BaseModel):
103
+ """Unable to extract a seat selection."""
104
+
105
+
106
+ # This agent is responsible for extracting the user's seat selection
107
+ seat_preference_agent = Agent[None, SeatPreference | Failed](
108
+ "openai:gpt-4o-mini",
109
+ output_type=SeatPreference | Failed,
110
+ system_prompt=(
111
+ "Extract the user's seat preference. "
112
+ "Seats A and F are window seats. "
113
+ "Row 1 is the front row and has extra leg room. "
114
+ "Rows 14, and 20 also have extra leg room. "
115
+ ),
116
+ )
117
+
118
+
119
+ # in reality this would be downloaded from a booking site,
120
+ # potentially using another agent to navigate the site
121
+ flights_web_page = """
122
+ 1. Flight SFO-AK123
123
+ - Price: $350
124
+ - Origin: San Francisco International Airport (SFO)
125
+ - Destination: Ted Stevens Anchorage International Airport (ANC)
126
+ - Date: January 10, 2025
127
+
128
+ 2. Flight SFO-AK456
129
+ - Price: $370
130
+ - Origin: San Francisco International Airport (SFO)
131
+ - Destination: Fairbanks International Airport (FAI)
132
+ - Date: January 10, 2025
133
+
134
+ 3. Flight SFO-AK789
135
+ - Price: $400
136
+ - Origin: San Francisco International Airport (SFO)
137
+ - Destination: Juneau International Airport (JNU)
138
+ - Date: January 20, 2025
139
+
140
+ 4. Flight NYC-LA101
141
+ - Price: $250
142
+ - Origin: San Francisco International Airport (SFO)
143
+ - Destination: Ted Stevens Anchorage International Airport (ANC)
144
+ - Date: January 10, 2025
145
+
146
+ 5. Flight CHI-MIA202
147
+ - Price: $200
148
+ - Origin: Chicago O'Hare International Airport (ORD)
149
+ - Destination: Miami International Airport (MIA)
150
+ - Date: January 12, 2025
151
+
152
+ 6. Flight BOS-SEA303
153
+ - Price: $120
154
+ - Origin: Boston Logan International Airport (BOS)
155
+ - Destination: Ted Stevens Anchorage International Airport (ANC)
156
+ - Date: January 12, 2025
157
+
158
+ 7. Flight DFW-DEN404
159
+ - Price: $150
160
+ - Origin: Dallas/Fort Worth International Airport (DFW)
161
+ - Destination: Denver International Airport (DEN)
162
+ - Date: January 10, 2025
163
+
164
+ 8. Flight ATL-HOU505
165
+ - Price: $180
166
+ - Origin: Hartsfield-Jackson Atlanta International Airport (ATL)
167
+ - Destination: George Bush Intercontinental Airport (IAH)
168
+ - Date: January 10, 2025
169
+ """
170
+
171
+ # restrict how many requests this app can make to the LLM
172
+ usage_limits = UsageLimits(request_limit=15)
173
+
174
+
175
+ @pixie.app
176
+ async def pydantic_ai_flight_booking() -> pixie.PixieGenerator[str, str]:
177
+ """Multi-agent flight booking with search, extraction, and seat selection.
178
+
179
+ This example demonstrates:
180
+ - Agent delegation (search_agent -> extraction_agent)
181
+ - Programmatic agent hand-off (search -> seat selection)
182
+ - Interactive user input for flight confirmation and seat preference
183
+ """
184
+
185
+ deps = Deps(
186
+ web_page_text=flights_web_page,
187
+ req_origin="SFO",
188
+ req_destination="ANC",
189
+ req_date=datetime.date(2025, 1, 10),
190
+ )
191
+ message_history: list[ModelMessage] | None = None
192
+ usage: RunUsage = RunUsage()
193
+
194
+ yield "🛫 Welcome to Flight Booking! Searching for flights..."
195
+
196
+ # Flight search loop
197
+ while True:
198
+ agent_result = await search_agent.run(
199
+ f"Find me a flight from {deps.req_origin} to {deps.req_destination} on {deps.req_date}",
200
+ deps=deps,
201
+ usage=usage,
202
+ message_history=message_history,
203
+ usage_limits=usage_limits,
204
+ )
205
+
206
+ if isinstance(agent_result.output, NoFlightFound):
207
+ yield "❌ No flight found. Ending search."
208
+ break
209
+ else:
210
+ flight = agent_result.output
211
+ yield f"✈️ Flight found: {flight.flight_number}"
212
+ yield f" Price: ${flight.price}"
213
+ yield f" Route: {flight.origin} → {flight.destination}"
214
+ yield f" Date: {flight.date}"
215
+ yield "\nDo you want to buy this flight, or keep searching? (buy/search)"
216
+
217
+ answer = yield pixie.InputRequired(str)
218
+ answer = answer.lower().strip()
219
+
220
+ if answer == "buy":
221
+ # Move to seat selection
222
+ yield "\n🪑 Great! Now let's select your seat..."
223
+ seat_message_history: list[ModelMessage] | None = None
224
+
225
+ while True:
226
+ yield 'What seat would you like? (e.g., "row 1 seat A" or "window seat with leg room")'
227
+ seat_input = yield pixie.InputRequired(str)
228
+
229
+ seat_result = await seat_preference_agent.run(
230
+ seat_input,
231
+ message_history=seat_message_history,
232
+ usage=usage,
233
+ usage_limits=usage_limits,
234
+ )
235
+
236
+ if isinstance(seat_result.output, SeatPreference):
237
+ seat = seat_result.output
238
+ yield "\n✅ Booking confirmed!"
239
+ yield f" Flight: {flight.flight_number}"
240
+ yield f" Seat: Row {seat.row}, Seat {seat.seat}"
241
+ yield f" Total Cost: ${flight.price}"
242
+ return
243
+ else:
244
+ yield "❌ Could not understand seat preference. Please try again."
245
+ seat_message_history = seat_result.all_messages()
246
+ else:
247
+ yield "\n🔄 Searching for another flight..."
248
+ message_history = agent_result.all_messages(
249
+ output_tool_return_content="Please suggest another flight"
250
+ )