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,152 @@
1
+ """Example of a graph-based state machine for asking and evaluating questions.
2
+
3
+ This demonstrates using pydantic_graph for complex control flow.
4
+ """
5
+
6
+ from dataclasses import dataclass, field
7
+
8
+ from pydantic import BaseModel
9
+ from pydantic_ai import Agent, ModelMessage, format_as_xml
10
+ from pydantic_graph import (
11
+ BaseNode,
12
+ End,
13
+ Graph,
14
+ GraphRunContext,
15
+ )
16
+ import pixie
17
+
18
+ # Agent for asking questions
19
+ ask_agent = Agent("openai:gpt-4o-mini", output_type=str)
20
+
21
+
22
+ @dataclass
23
+ class QuestionState:
24
+ question: str | None = None
25
+ answer: str | None = None
26
+ ask_agent_messages: list[ModelMessage] = field(default_factory=list)
27
+ evaluate_agent_messages: list[ModelMessage] = field(default_factory=list)
28
+
29
+
30
+ @dataclass
31
+ class Ask(BaseNode[QuestionState]):
32
+ """Generate a question using the AI."""
33
+
34
+ async def run(self, ctx: GraphRunContext[QuestionState]) -> "Answer":
35
+ agent_result = await ask_agent.run(
36
+ "Ask a simple question with a single correct answer.",
37
+ message_history=ctx.state.ask_agent_messages,
38
+ )
39
+ ctx.state.ask_agent_messages += agent_result.all_messages()
40
+ ctx.state.question = agent_result.output
41
+ ctx.state.answer = None
42
+ return Answer(agent_result.output)
43
+
44
+
45
+ @dataclass
46
+ class Answer(BaseNode[QuestionState]):
47
+ """Wait for user to provide an answer."""
48
+
49
+ question: str
50
+
51
+ async def run(self, ctx: GraphRunContext[QuestionState]) -> "Evaluate":
52
+ if ctx.state.answer is None:
53
+ raise ValueError("Answer need to be set after Ask() node in the main loop.")
54
+ return Evaluate(ctx.state.answer)
55
+
56
+
57
+ class EvaluationOutput(BaseModel, use_attribute_docstrings=True):
58
+ correct: bool
59
+ """Whether the answer is correct."""
60
+ comment: str
61
+ """Comment on the answer, reprimand the user if the answer is wrong."""
62
+
63
+
64
+ # Agent for evaluating answers
65
+ evaluate_agent = Agent(
66
+ "openai:gpt-4o-mini",
67
+ output_type=EvaluationOutput,
68
+ system_prompt="Given a question and answer, evaluate if the answer is correct.",
69
+ )
70
+
71
+
72
+ @dataclass
73
+ class Evaluate(BaseNode[QuestionState, None, str]):
74
+ """Evaluate the user's answer."""
75
+
76
+ answer: str
77
+
78
+ async def run(
79
+ self,
80
+ ctx: GraphRunContext[QuestionState],
81
+ ) -> End[str] | "Reprimand":
82
+ assert ctx.state.question is not None
83
+ agent_result = await evaluate_agent.run(
84
+ format_as_xml({"question": ctx.state.question, "answer": self.answer}),
85
+ message_history=ctx.state.evaluate_agent_messages,
86
+ )
87
+ ctx.state.evaluate_agent_messages += agent_result.all_messages()
88
+ if agent_result.output.correct:
89
+ return End(agent_result.output.comment)
90
+ else:
91
+ return Reprimand(agent_result.output.comment)
92
+
93
+
94
+ @dataclass
95
+ class Reprimand(BaseNode[QuestionState]):
96
+ """Tell the user they got it wrong and ask another question."""
97
+
98
+ comment: str
99
+
100
+ async def run(self, ctx: GraphRunContext[QuestionState]) -> Ask:
101
+ ctx.state.question = None
102
+ return Ask()
103
+
104
+
105
+ # Create the question graph
106
+ _question_graph = Graph(
107
+ nodes=(Ask, Answer, Evaluate, Reprimand), state_type=QuestionState
108
+ )
109
+
110
+
111
+ @pixie.app
112
+ async def pydantic_ai_question_graph() -> pixie.PixieGenerator[str, str]:
113
+ """Interactive Q&A game using graph-based state machine.
114
+
115
+ The AI asks questions, the user answers, and the AI evaluates.
116
+ If wrong, user gets reprimanded and a new question is asked.
117
+ """
118
+
119
+ yield "🧠 Welcome to the Q&A Challenge!"
120
+ yield "I'll ask you questions and evaluate your answers.\n"
121
+
122
+ state = QuestionState()
123
+
124
+ # Use the graph's run_sync method which handles node execution
125
+ async with _question_graph.iter(Ask(), state=state) as graph_ctx:
126
+ while True:
127
+ node = await graph_ctx.next()
128
+
129
+ # Handle different node types
130
+ if isinstance(node, Ask):
131
+ yield "🤔 Generating a question..."
132
+
133
+ elif isinstance(node, Answer):
134
+ # Ask user for their answer
135
+ yield f"\n❓ Question: {node.question}"
136
+ yield "What is your answer?"
137
+ state.answer = yield pixie.InputRequired(str)
138
+ # Continue with the user's answer by passing it to the next iteration
139
+ # The graph will automatically move to Evaluate with this answer
140
+ continue
141
+
142
+ elif isinstance(node, Evaluate):
143
+ yield "⏳ Evaluating your answer..."
144
+
145
+ elif isinstance(node, Reprimand):
146
+ yield f"\n❌ {node.comment}"
147
+ yield "\n🔄 Let's try another question...\n"
148
+
149
+ elif isinstance(node, End):
150
+ yield f"\n✅ {node.data}"
151
+ yield "\n🎉 Congratulations! You got it right!"
152
+ return
@@ -0,0 +1,182 @@
1
+ """Example demonstrating SQL generation using Pydantic AI.
2
+
3
+ This shows multi-step workflow with validation and structured output.
4
+ """
5
+
6
+ from dataclasses import dataclass
7
+ from datetime import date
8
+ from typing import Annotated, TypeAlias
9
+
10
+ from annotated_types import MinLen
11
+ from pydantic import BaseModel, Field
12
+ from pydantic_ai import Agent, ModelRetry, RunContext, format_as_xml
13
+ import pixie
14
+
15
+
16
+ # Simulated database schema
17
+ DB_SCHEMA = """
18
+ CREATE TABLE records (
19
+ id bigint PRIMARY KEY,
20
+ created_at timestamptz,
21
+ start_timestamp timestamptz,
22
+ end_timestamp timestamptz,
23
+ trace_id text,
24
+ span_id text,
25
+ parent_span_id text,
26
+ level log_level,
27
+ span_name text,
28
+ message text,
29
+ attributes_json_schema text,
30
+ attributes jsonb,
31
+ tags text[],
32
+ is_exception boolean,
33
+ otel_status_message text,
34
+ service_name text
35
+ );
36
+ """
37
+
38
+ SQL_EXAMPLES = [
39
+ {
40
+ "request": "show me records where foobar is false",
41
+ "response": "SELECT * FROM records WHERE attributes->>'foobar' = false",
42
+ },
43
+ {
44
+ "request": 'show me records where attributes include the key "foobar"',
45
+ "response": "SELECT * FROM records WHERE attributes ? 'foobar'",
46
+ },
47
+ {
48
+ "request": "show me records from yesterday",
49
+ "response": "SELECT * FROM records WHERE start_timestamp::date > CURRENT_TIMESTAMP - INTERVAL '1 day'",
50
+ },
51
+ {
52
+ "request": 'show me error records with the tag "foobar"',
53
+ "response": "SELECT * FROM records WHERE level = 'error' and 'foobar' = ANY(tags)",
54
+ },
55
+ ]
56
+
57
+
58
+ @dataclass
59
+ class Deps:
60
+ """Dependencies for SQL generation (simulated database connection)."""
61
+
62
+ validate_sql: bool = True
63
+
64
+
65
+ class Success(BaseModel):
66
+ """Response when SQL could be successfully generated."""
67
+
68
+ sql_query: Annotated[str, MinLen(1)]
69
+ explanation: str = Field(
70
+ "", description="Explanation of the SQL query, as markdown"
71
+ )
72
+
73
+
74
+ class InvalidRequest(BaseModel):
75
+ """Response when the user input didn't include enough information to generate SQL."""
76
+
77
+ error_message: str
78
+
79
+
80
+ Response: TypeAlias = Success | InvalidRequest
81
+
82
+ agent = Agent[Deps, Response](
83
+ "openai:gpt-4o-mini",
84
+ output_type=Response, # type: ignore
85
+ deps_type=Deps,
86
+ )
87
+
88
+
89
+ @agent.system_prompt
90
+ async def system_prompt() -> str:
91
+ return f"""\
92
+ Given the following PostgreSQL table of records, your job is to
93
+ write a SQL query that suits the user's request.
94
+
95
+ Database schema:
96
+
97
+ {DB_SCHEMA}
98
+
99
+ today's date = {date.today()}
100
+
101
+ {format_as_xml(SQL_EXAMPLES)}
102
+ """
103
+
104
+
105
+ @agent.output_validator
106
+ async def validate_output(ctx: RunContext[Deps], output: Response) -> Response:
107
+ if isinstance(output, InvalidRequest):
108
+ return output
109
+
110
+ # Clean up the SQL query
111
+ output.sql_query = output.sql_query.replace("\\", "")
112
+ if not output.sql_query.upper().startswith("SELECT"):
113
+ raise ModelRetry("Please create a SELECT query")
114
+
115
+ # In a real scenario, we'd validate against an actual database
116
+ # For now, we'll do basic validation
117
+ if ctx.deps.validate_sql:
118
+ # Check for dangerous operations
119
+ upper_query = output.sql_query.upper()
120
+ if any(
121
+ danger in upper_query
122
+ for danger in ["DROP", "DELETE", "UPDATE", "INSERT", "TRUNCATE"]
123
+ ):
124
+ raise ModelRetry("Only SELECT queries are allowed")
125
+
126
+ # Check for required table reference
127
+ if "FROM RECORDS" not in upper_query and 'FROM "RECORDS"' not in upper_query:
128
+ raise ModelRetry("Query must reference the records table")
129
+
130
+ return output
131
+
132
+
133
+ @pixie.app
134
+ async def pydantic_ai_sql_gen(query: str) -> str:
135
+ """SQL generation agent with multi-step workflow and validation.
136
+
137
+ This example demonstrates:
138
+ - Dynamic system prompts with examples
139
+ - Output validation with ModelRetry
140
+ - Structured output with union types
141
+ - Agent dependencies for configuration
142
+
143
+ Args:
144
+ query: Natural language description of desired SQL query
145
+
146
+ Returns:
147
+ Generated SQL query or error message
148
+ """
149
+
150
+ deps = Deps(validate_sql=True)
151
+ result = await agent.run(query, deps=deps)
152
+
153
+ if isinstance(result.output, Success):
154
+ response = f"✅ SQL Query Generated:\n\n{result.output.sql_query}"
155
+ if result.output.explanation:
156
+ response += f"\n\n📝 Explanation:\n{result.output.explanation}"
157
+ return response
158
+ else:
159
+ return f"❌ Error: {result.output.error_message}"
160
+
161
+
162
+ # For local testing
163
+ async def test():
164
+ """Test function for local development."""
165
+ test_queries = [
166
+ "show me logs from yesterday with level 'error'",
167
+ "find all records where foobar is true",
168
+ "show me records from the last week",
169
+ ]
170
+
171
+ for query in test_queries:
172
+ print(f"\n{'='*60}")
173
+ print(f"Query: {query}")
174
+ print("=" * 60)
175
+ result = await pydantic_ai_sql_gen(query)
176
+ print(result)
177
+
178
+
179
+ if __name__ == "__main__":
180
+ import asyncio
181
+
182
+ asyncio.run(test())
@@ -0,0 +1,64 @@
1
+ """Simple example of using Pydantic AI to construct a Pydantic model from a text input.
2
+
3
+ This example demonstrates:
4
+ - Using Pydantic AI with structured output
5
+ - Integration with Pixie SDK for observability
6
+
7
+ Run with:
8
+ poetry run pixie
9
+
10
+ Then query via GraphQL:
11
+ subscription {
12
+ run(name: "pydantic_model_example", inputData: "The windy city in the US of A.") {
13
+ runId
14
+ status
15
+ data
16
+ }
17
+ }
18
+ """
19
+
20
+ import os
21
+ from pydantic import BaseModel
22
+ from pydantic_ai import Agent
23
+ import pixie
24
+
25
+
26
+ class MyModel(BaseModel):
27
+ """Model representing a city and country."""
28
+
29
+ city: str
30
+ country: str
31
+
32
+
33
+ # Create the agent
34
+ model = os.getenv("PYDANTIC_AI_MODEL", "openai:gpt-4o-mini")
35
+ agent = Agent(model, output_type=MyModel)
36
+
37
+
38
+ @pixie.app
39
+ async def pydantic_ai_structured_output(query: str) -> MyModel:
40
+ """Extract city and country information from a text query.
41
+
42
+ Args:
43
+ query: Natural language text describing a city
44
+
45
+ Returns:
46
+ MyModel with extracted city and country
47
+ """
48
+
49
+ # Run the agent
50
+ agent_result = await agent.run(query)
51
+
52
+ # Return the structured output
53
+ return agent_result.output
54
+
55
+
56
+ if __name__ == "__main__":
57
+ # For testing locally
58
+ import asyncio
59
+
60
+ async def test():
61
+ output = await pydantic_ai_structured_output("The windy city in the US of A.")
62
+ print(output)
63
+
64
+ asyncio.run(test())
File without changes
@@ -0,0 +1,25 @@
1
+ from pydantic_ai import Agent
2
+
3
+ import pixie
4
+
5
+ agent = Agent(
6
+ name="Simple chatbot",
7
+ instructions="You are a helpful assistant.",
8
+ model="gpt-4o-mini",
9
+ )
10
+
11
+
12
+ @pixie.app
13
+ async def example_chatbot():
14
+ """A simple chatbot using Pydantic-AI agent with GPT-4o-mini.
15
+
16
+ An OpenAI API key environment variable *(`OPENAI_API_KEY`)* is required to run this example.
17
+ """
18
+
19
+ yield "How can I help you today?"
20
+ messages = []
21
+ while True:
22
+ user_msg = yield pixie.InputRequired(str)
23
+ response = await agent.run(user_msg, message_history=messages)
24
+ messages = response.all_messages()
25
+ yield response.output
@@ -0,0 +1,96 @@
1
+ """An interactive agent that writes haikus while taking naps."""
2
+
3
+ import asyncio
4
+ from dataclasses import dataclass
5
+ import logging
6
+ from pydantic import BaseModel, Field
7
+ from pydantic_ai import Agent, RunContext
8
+ from pydantic_ai.models.openai import OpenAIChatModelSettings
9
+
10
+ import pixie
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+
15
+ @dataclass
16
+ class PoetDeps:
17
+ """Dependencies for the sleepy poet agent."""
18
+
19
+ queue: asyncio.Queue
20
+ time_to_write: bool = False
21
+
22
+
23
+ poet = Agent(
24
+ "openai:gpt-4o-mini",
25
+ system_prompt=(
26
+ "You are a sleepy poet. When given a topic, you first take a nap by calling the "
27
+ "`sleep_for_a_bit` tool once and once only, then write a haiku about the topic."
28
+ ),
29
+ deps_type=PoetDeps,
30
+ model_settings=OpenAIChatModelSettings(
31
+ parallel_tool_calls=False # Ensure single sleep tool call per turn
32
+ ),
33
+ )
34
+
35
+
36
+ @poet.tool
37
+ async def sleep_for_a_bit(ctx: RunContext[PoetDeps]) -> str:
38
+ """Sleep for 5 seconds before writing a haiku."""
39
+ if ctx.deps.time_to_write:
40
+ return "I've already napped, It's time to write the haiku now."
41
+
42
+ await ctx.deps.queue.put("Let me take a nap...")
43
+ for i in range(3):
44
+ await asyncio.sleep(1)
45
+ await ctx.deps.queue.put("z" * (i + 1))
46
+ ctx.deps.time_to_write = True
47
+ return "Poet napped for 3 seconds."
48
+
49
+
50
+ class HaikuRequest(BaseModel):
51
+ """Input config for sleepy haiku agent."""
52
+
53
+ topic: str = Field(..., description="The topic to write haikus about.")
54
+ count: int = Field(3, description="Number of haikus to write.")
55
+
56
+
57
+ @pixie.app
58
+ async def example_sleepy_poet() -> pixie.PixieGenerator[str, HaikuRequest]:
59
+ """An interactive agent that writes haikus while taking naps.
60
+
61
+ This agent accepts a topic and number of haikus to write, then for each haiku,
62
+ it first calls a tool to sleep for a few seconds before composing the haiku.
63
+
64
+ Yields:
65
+ HaikuRequest: Requirement for user's input for the haiku request.
66
+ str: The generated haikus interleaved with sleep updates.
67
+ """
68
+
69
+ q = asyncio.Queue[str | None]()
70
+ deps = PoetDeps(queue=q)
71
+
72
+ yield "I'm the best poet in town! I can write haikus on any topic."
73
+
74
+ while True:
75
+ yield "What's your request?"
76
+ config = yield pixie.InputRequired(HaikuRequest)
77
+
78
+ async def write_haikus():
79
+ for i in range(0, config.count):
80
+ result = await poet.run(config.topic, deps=deps)
81
+ await q.put(f"### Haiku #{i+1}\n{result.output}\n")
82
+ deps.time_to_write = False # Reset for next haiku
83
+
84
+ await q.put(None) # Signal completion
85
+
86
+ task = asyncio.create_task(write_haikus())
87
+ try:
88
+ while True:
89
+ update = await q.get()
90
+ if update is None:
91
+ break
92
+ yield update
93
+
94
+ except asyncio.CancelledError:
95
+ task.cancel()
96
+ return
@@ -0,0 +1,110 @@
1
+ """Interactive weather agent."""
2
+
3
+ import asyncio
4
+ from dataclasses import dataclass
5
+ from typing import Any
6
+ from httpx import AsyncClient
7
+ from pydantic import BaseModel
8
+ from pydantic_ai import Agent, RunContext
9
+ import pixie
10
+
11
+
12
+ @dataclass
13
+ class Deps:
14
+ """Dependencies for the weather agent."""
15
+
16
+ client: AsyncClient
17
+
18
+
19
+ class LatLng(BaseModel):
20
+ """Latitude and longitude coordinates."""
21
+
22
+ lat: float
23
+ lng: float
24
+
25
+
26
+ # Create the weather agent
27
+ agent = Agent(
28
+ "openai:gpt-4o-mini",
29
+ instructions="Be concise, reply with one sentence.",
30
+ deps_type=Deps,
31
+ retries=2,
32
+ )
33
+
34
+
35
+ @agent.tool
36
+ async def get_lat_lng(ctx: RunContext[Deps], location_description: str) -> LatLng:
37
+ """Get the latitude and longitude of a location.
38
+
39
+ Args:
40
+ ctx: The context.
41
+ location_description: A description of a location.
42
+ """
43
+ # NOTE: Uses demo endpoints that return random data
44
+ r = await ctx.deps.client.get(
45
+ "https://demo-endpoints.pydantic.workers.dev/latlng",
46
+ params={"location": location_description},
47
+ )
48
+ r.raise_for_status()
49
+ return LatLng.model_validate_json(r.content)
50
+
51
+
52
+ @agent.tool
53
+ async def get_weather(ctx: RunContext[Deps], lat: float, lng: float) -> dict[str, Any]:
54
+ """Get the weather at a location.
55
+
56
+ Args:
57
+ ctx: The context.
58
+ lat: Latitude of the location.
59
+ lng: Longitude of the location.
60
+ """
61
+ # NOTE: Uses demo endpoints that return random data
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},
66
+ ),
67
+ ctx.deps.client.get(
68
+ "https://demo-endpoints.pydantic.workers.dev/weather",
69
+ params={"lat": lat, "lng": lng},
70
+ ),
71
+ )
72
+ temp_response.raise_for_status()
73
+ descr_response.raise_for_status()
74
+ return {
75
+ "temperature": f"{temp_response.text} °C",
76
+ "description": descr_response.text,
77
+ }
78
+
79
+
80
+ @pixie.app
81
+ async def example_weather_agent() -> pixie.PixieGenerator[str, str]:
82
+ """Interactive weather agent.
83
+
84
+ This agent interacts with the user to get weather information for a specified location.
85
+ It uses tools to fetch latitude/longitude and weather data, and supports a multi-turn
86
+ conversation with the user.
87
+
88
+ The agent will:
89
+ - Prompt the user for a location.
90
+ - Fetch latitude and longitude for the location.
91
+ - Retrieve weather data for the coordinates.
92
+ - Display the weather information to the user.
93
+
94
+ The conversation continues until the user enters a stop word ('exit', 'quit', or 'stop').
95
+ """
96
+
97
+ yield "Hi! I can help you find the weather for any location."
98
+ yield "Enter 'exit', 'quit', or 'stop' to end the conversation."
99
+
100
+ # Create HTTP client and dependencies
101
+ async with AsyncClient() as client:
102
+ deps = Deps(client=client)
103
+
104
+ user_message = None
105
+ stop_words = ["exit", "quit", "stop"]
106
+ while user_message not in stop_words:
107
+ yield "What location would you like the weather for?"
108
+ user_message = yield pixie.InputRequired(str)
109
+ result = await agent.run(user_message, deps=deps)
110
+ yield result.output