pixie-examples 0.1.1.dev5__tar.gz → 0.1.1.dev14__tar.gz

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.
Files changed (34) hide show
  1. {pixie_examples-0.1.1.dev5 → pixie_examples-0.1.1.dev14}/PKG-INFO +2 -2
  2. {pixie_examples-0.1.1.dev5 → pixie_examples-0.1.1.dev14}/examples/langchain/basic_agent.py +11 -2
  3. {pixie_examples-0.1.1.dev5 → pixie_examples-0.1.1.dev14}/examples/langchain/customer_support.py +33 -35
  4. {pixie_examples-0.1.1.dev5 → pixie_examples-0.1.1.dev14}/examples/langchain/personal_assistant.py +16 -29
  5. {pixie_examples-0.1.1.dev5 → pixie_examples-0.1.1.dev14}/examples/langchain/sql_agent.py +15 -25
  6. {pixie_examples-0.1.1.dev5 → pixie_examples-0.1.1.dev14}/examples/langgraph/langgraph_rag.py +43 -35
  7. {pixie_examples-0.1.1.dev5 → pixie_examples-0.1.1.dev14}/examples/langgraph/langgraph_sql_agent.py +23 -34
  8. {pixie_examples-0.1.1.dev5 → pixie_examples-0.1.1.dev14}/examples/openai_agents_sdk/customer_service.py +55 -46
  9. {pixie_examples-0.1.1.dev5 → pixie_examples-0.1.1.dev14}/examples/openai_agents_sdk/financial_research_agent.py +109 -91
  10. {pixie_examples-0.1.1.dev5 → pixie_examples-0.1.1.dev14}/examples/openai_agents_sdk/llm_as_a_judge.py +20 -20
  11. {pixie_examples-0.1.1.dev5 → pixie_examples-0.1.1.dev14}/examples/openai_agents_sdk/routing.py +62 -19
  12. {pixie_examples-0.1.1.dev5 → pixie_examples-0.1.1.dev14}/examples/pydantic_ai/bank_support.py +29 -28
  13. {pixie_examples-0.1.1.dev5 → pixie_examples-0.1.1.dev14}/examples/pydantic_ai/flight_booking.py +70 -66
  14. {pixie_examples-0.1.1.dev5 → pixie_examples-0.1.1.dev14}/examples/pydantic_ai/question_graph.py +42 -19
  15. {pixie_examples-0.1.1.dev5 → pixie_examples-0.1.1.dev14}/examples/pydantic_ai/sql_gen.py +20 -12
  16. pixie_examples-0.1.1.dev14/examples/quickstart/problem_solver.py +38 -0
  17. {pixie_examples-0.1.1.dev5 → pixie_examples-0.1.1.dev14}/examples/quickstart/sleepy_poet.py +27 -27
  18. {pixie_examples-0.1.1.dev5 → pixie_examples-0.1.1.dev14}/pyproject.toml +2 -2
  19. {pixie_examples-0.1.1.dev5 → pixie_examples-0.1.1.dev14}/LICENSE +0 -0
  20. {pixie_examples-0.1.1.dev5 → pixie_examples-0.1.1.dev14}/README.md +0 -0
  21. {pixie_examples-0.1.1.dev5 → pixie_examples-0.1.1.dev14}/examples/__init__.py +0 -0
  22. {pixie_examples-0.1.1.dev5 → pixie_examples-0.1.1.dev14}/examples/langchain/README.md +0 -0
  23. {pixie_examples-0.1.1.dev5 → pixie_examples-0.1.1.dev14}/examples/langchain/__init__.py +0 -0
  24. {pixie_examples-0.1.1.dev5 → pixie_examples-0.1.1.dev14}/examples/langgraph/__init__.py +0 -0
  25. {pixie_examples-0.1.1.dev5 → pixie_examples-0.1.1.dev14}/examples/openai_agents_sdk/README.md +0 -0
  26. {pixie_examples-0.1.1.dev5 → pixie_examples-0.1.1.dev14}/examples/openai_agents_sdk/__init__.py +0 -0
  27. {pixie_examples-0.1.1.dev5 → pixie_examples-0.1.1.dev14}/examples/pydantic_ai/.env.example +0 -0
  28. {pixie_examples-0.1.1.dev5 → pixie_examples-0.1.1.dev14}/examples/pydantic_ai/README.md +0 -0
  29. {pixie_examples-0.1.1.dev5 → pixie_examples-0.1.1.dev14}/examples/pydantic_ai/__init__.py +0 -0
  30. {pixie_examples-0.1.1.dev5 → pixie_examples-0.1.1.dev14}/examples/pydantic_ai/structured_output.py +0 -0
  31. {pixie_examples-0.1.1.dev5 → pixie_examples-0.1.1.dev14}/examples/quickstart/__init__.py +0 -0
  32. {pixie_examples-0.1.1.dev5 → pixie_examples-0.1.1.dev14}/examples/quickstart/chatbot.py +0 -0
  33. {pixie_examples-0.1.1.dev5 → pixie_examples-0.1.1.dev14}/examples/quickstart/weather_agent.py +0 -0
  34. {pixie_examples-0.1.1.dev5 → pixie_examples-0.1.1.dev14}/examples/sql_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pixie-examples
3
- Version: 0.1.1.dev5
3
+ Version: 0.1.1.dev14
4
4
  Summary: examples for using Pixie
5
5
  License: MIT
6
6
  License-File: LICENSE
@@ -24,7 +24,7 @@ Requires-Dist: openinference-instrumentation-crewai (>=0.1.17,<0.2.0)
24
24
  Requires-Dist: openinference-instrumentation-dspy (>=0.1.33,<0.2.0)
25
25
  Requires-Dist: openinference-instrumentation-google-adk (>=0.1.8,<0.2.0)
26
26
  Requires-Dist: openinference-instrumentation-openai-agents (>=1.4.0,<2.0.0)
27
- Requires-Dist: pixie-sdk (>=0.1.1.dev13,<0.2.0)
27
+ Requires-Dist: pixie-sdk (>=0.1.1.dev36,<0.2.0)
28
28
  Requires-Dist: pydantic (>=2.7.4,<3.0.0)
29
29
  Requires-Dist: pydantic-ai-slim (>=1.39.0,<2.0.0)
30
30
  Requires-Dist: pymarkdownlnt (>=0.9.34,<0.10.0)
@@ -12,6 +12,15 @@ from langfuse.langchain import CallbackHandler
12
12
  import pixie
13
13
 
14
14
 
15
+ basic_weather_agent_prompt = pixie.create_prompt(
16
+ "basic_weather_agent",
17
+ description="Helpful weather assistant",
18
+ )
19
+ interactive_weather_agent_prompt = pixie.create_prompt(
20
+ "interactive_weather_agent",
21
+ description="Interactive weather assistant that answers questions about weather",
22
+ )
23
+
15
24
  langfuse_handler = CallbackHandler()
16
25
 
17
26
 
@@ -37,7 +46,7 @@ async def langchain_basic_weather_agent(query: str) -> str:
37
46
  agent = create_agent(
38
47
  model=model,
39
48
  tools=[get_weather],
40
- system_prompt="You are a helpful weather assistant",
49
+ system_prompt=basic_weather_agent_prompt.compile(),
41
50
  )
42
51
 
43
52
  # Run the agent
@@ -66,7 +75,7 @@ async def langchain_interactive_weather_agent() -> pixie.PixieGenerator[str, str
66
75
  agent = create_agent(
67
76
  model=model,
68
77
  tools=[get_weather],
69
- system_prompt="You are a helpful weather assistant that answers questions about weather.",
78
+ system_prompt=interactive_weather_agent_prompt.compile(),
70
79
  )
71
80
 
72
81
  # Send welcome message
@@ -7,7 +7,7 @@ as it moves through different states of a workflow.
7
7
  Based on: https://docs.langchain.com/oss/python/langchain/multi-agent/handoffs-customer-support
8
8
  """
9
9
 
10
- from typing import Literal, NotRequired
10
+ from typing import Literal, NotRequired, cast
11
11
  from langchain.agents import create_agent, AgentState
12
12
  from langchain.chat_models import init_chat_model
13
13
  from langchain.tools import tool, ToolRuntime
@@ -17,6 +17,9 @@ from langgraph.types import Command
17
17
  from typing import Callable
18
18
 
19
19
  from langfuse.langchain import CallbackHandler
20
+ from langchain_core.messages import (
21
+ SystemMessage,
22
+ )
20
23
  import pixie
21
24
 
22
25
 
@@ -90,54 +93,42 @@ def provide_solution(solution: str) -> str:
90
93
  return f"Solution provided: {solution}"
91
94
 
92
95
 
93
- # Step prompts
94
- WARRANTY_COLLECTOR_PROMPT = """You are a customer support agent collecting warranty information.
96
+ class IssueClassifierPromptVariables(pixie.PromptVariables):
97
+ warranty_status: Literal["in_warranty", "out_of_warranty"]
95
98
 
96
- CURRENT STAGE: Warranty Verification
97
99
 
98
- Ask the customer if their device is under warranty. Once you have this information,
99
- use the record_warranty_status tool to record it and move to the next step.
100
+ class ResolutionSpecialistPromptVariables(IssueClassifierPromptVariables):
101
+ issue_type: Literal["hardware", "software"]
100
102
 
101
- Be polite and professional."""
102
-
103
- ISSUE_CLASSIFIER_PROMPT = """You are a customer support agent classifying technical issues.
104
-
105
- CURRENT STAGE: Issue Classification
106
- CUSTOMER INFO: Warranty status is {warranty_status}
107
-
108
- Ask the customer to describe their issue, then determine if it's:
109
- - HARDWARE: Physical problems (cracked screen, battery, ports, buttons)
110
- - SOFTWARE: App crashes, performance, settings, updates
111
-
112
- Use record_issue_type to record the classification and move to resolution."""
113
-
114
- RESOLUTION_SPECIALIST_PROMPT = """You are a customer support agent helping with device issues.
115
-
116
- CURRENT STAGE: Resolution
117
- CUSTOMER INFO: Warranty status is {warranty_status}, issue type is {issue_type}
118
-
119
- At this step, you need to:
120
- 1. For SOFTWARE issues: provide troubleshooting steps using provide_solution
121
- 2. For HARDWARE issues:
122
- - If IN WARRANTY: explain warranty repair process using provide_solution
123
- - If OUT OF WARRANTY: escalate_to_human for paid repair options
124
-
125
- Be specific and helpful in your solutions."""
126
103
 
104
+ warranty_collector_prompt = pixie.create_prompt(
105
+ "warranty_collector_agent",
106
+ description="Customer support agent that collects warranty information",
107
+ )
108
+ issue_classifier_prompt = pixie.create_prompt(
109
+ "issue_classifier_agent",
110
+ IssueClassifierPromptVariables,
111
+ description="Customer support agent that classifies technical issues as hardware or software",
112
+ )
113
+ resolution_specialist_prompt = pixie.create_prompt(
114
+ "resolution_specialist_agent",
115
+ ResolutionSpecialistPromptVariables,
116
+ description="Customer support agent that provides resolutions based on issue type and warranty status",
117
+ )
127
118
  # Step configuration
128
119
  STEP_CONFIG = {
129
120
  "warranty_collector": {
130
- "prompt": WARRANTY_COLLECTOR_PROMPT,
121
+ "prompt": warranty_collector_prompt,
131
122
  "tools": [record_warranty_status],
132
123
  "requires": [],
133
124
  },
134
125
  "issue_classifier": {
135
- "prompt": ISSUE_CLASSIFIER_PROMPT,
126
+ "prompt": issue_classifier_prompt,
136
127
  "tools": [record_issue_type],
137
128
  "requires": ["warranty_status"],
138
129
  },
139
130
  "resolution_specialist": {
140
- "prompt": RESOLUTION_SPECIALIST_PROMPT,
131
+ "prompt": resolution_specialist_prompt,
141
132
  "tools": [provide_solution, escalate_to_human],
142
133
  "requires": ["warranty_status", "issue_type"],
143
134
  },
@@ -165,7 +156,14 @@ def apply_step_config(
165
156
  # Note: In a production implementation, you would inject the formatted prompt
166
157
  # and tools into the request. For simplicity, we'll let the handler process
167
158
  # the request and handle tool selection based on state.
168
- _ = stage_config["prompt"].format(**request.state)
159
+ prompt = cast(pixie.Prompt, stage_config["prompt"])
160
+ if prompt.variables_definition:
161
+ vars = prompt.variables_definition(**request.state)
162
+ prompt_txt = prompt.compile(vars)
163
+ else:
164
+ prompt_txt = prompt.compile(None)
165
+
166
+ request.system_message = SystemMessage(prompt_txt)
169
167
 
170
168
  # The middleware pattern here would need deeper integration with LangChain's
171
169
  # internal APIs. For now, we pass through to the handler.
@@ -16,6 +16,19 @@ from langfuse.langchain import CallbackHandler
16
16
  import pixie
17
17
 
18
18
 
19
+ calendar_agent_prompt = pixie.create_prompt(
20
+ "calendar_agent",
21
+ description="Calendar scheduling assistant that parses natural language requests",
22
+ )
23
+ email_agent_prompt = pixie.create_prompt(
24
+ "email_agent",
25
+ description="Email assistant that composes and sends professional emails",
26
+ )
27
+ supervisor_agent_prompt = pixie.create_prompt(
28
+ "supervisor_agent",
29
+ description="Personal assistant coordinator that handles calendar and email tasks",
30
+ )
31
+
19
32
  langfuse_handler = CallbackHandler()
20
33
 
21
34
 
@@ -46,32 +59,6 @@ def send_email(to: list[str], subject: str, body: str, cc: list[str] = []) -> st
46
59
  return f"Email sent to {', '.join(to)} - Subject: {subject}"
47
60
 
48
61
 
49
- # System prompts for specialized agents
50
- CALENDAR_AGENT_PROMPT = (
51
- "You are a calendar scheduling assistant. "
52
- "Parse natural language scheduling requests (e.g., 'next Tuesday at 2pm') "
53
- "into proper ISO datetime formats. "
54
- "Use get_available_time_slots to check availability when needed. "
55
- "Use create_calendar_event to schedule events. "
56
- "Always confirm what was scheduled in your final response."
57
- )
58
-
59
- EMAIL_AGENT_PROMPT = (
60
- "You are an email assistant. "
61
- "Compose professional emails based on natural language requests. "
62
- "Extract recipient information and craft appropriate subject lines and body text. "
63
- "Use send_email to send the message. "
64
- "Always confirm what was sent in your final response."
65
- )
66
-
67
- SUPERVISOR_PROMPT = (
68
- "You are a helpful personal assistant. "
69
- "You can schedule calendar events and send emails. "
70
- "Break down user requests into appropriate tool calls and coordinate the results. "
71
- "When a request involves multiple actions, use multiple tools in sequence."
72
- )
73
-
74
-
75
62
  @pixie.app
76
63
  async def langchain_personal_assistant() -> pixie.PixieGenerator[str, str]:
77
64
  """Multi-agent personal assistant with calendar and email subagents.
@@ -90,14 +77,14 @@ async def langchain_personal_assistant() -> pixie.PixieGenerator[str, str]:
90
77
  calendar_agent = create_agent(
91
78
  model,
92
79
  tools=[create_calendar_event, get_available_time_slots],
93
- system_prompt=CALENDAR_AGENT_PROMPT,
80
+ system_prompt=calendar_agent_prompt.compile(),
94
81
  )
95
82
 
96
83
  # Create email subagent
97
84
  email_agent = create_agent(
98
85
  model,
99
86
  tools=[send_email],
100
- system_prompt=EMAIL_AGENT_PROMPT,
87
+ system_prompt=email_agent_prompt.compile(),
101
88
  )
102
89
 
103
90
  # Wrap subagents as tools for the supervisor
@@ -131,7 +118,7 @@ async def langchain_personal_assistant() -> pixie.PixieGenerator[str, str]:
131
118
  supervisor_agent = create_agent(
132
119
  model,
133
120
  tools=[schedule_event, manage_email],
134
- system_prompt=SUPERVISOR_PROMPT,
121
+ system_prompt=supervisor_agent_prompt.compile(),
135
122
  checkpointer=InMemorySaver(),
136
123
  )
137
124
 
@@ -26,32 +26,18 @@ import pixie
26
26
  from ..sql_utils import SQLDatabase, SQLDatabaseToolkit
27
27
 
28
28
 
29
- langfuse_handler = CallbackHandler()
30
-
31
-
32
- # System prompt for SQL agent
33
- SQL_AGENT_PROMPT = """
34
- You are an agent designed to interact with a SQL database.
35
- Given an input question, create a syntactically correct {dialect} query to run,
36
- then look at the results of the query and return the answer. Unless the user
37
- specifies a specific number of examples they wish to obtain, always limit your
38
- query to at most {top_k} results.
39
-
40
- You can order the results by a relevant column to return the most interesting
41
- examples in the database. Never query for all the columns from a specific table,
42
- only ask for the relevant columns given the question.
43
-
44
- You MUST double check your query before executing it. If you get an error while
45
- executing a query, rewrite the query and try again.
29
+ class SqlAgentPromptVariables(pixie.PromptVariables):
30
+ dialect: str
31
+ top_k: int
46
32
 
47
- DO NOT make any DML statements (INSERT, UPDATE, DELETE, DROP etc.) to the
48
- database.
49
33
 
50
- To start you should ALWAYS look at the tables in the database to see what you
51
- can query. Do NOT skip this step.
34
+ sql_agent_prompt = pixie.create_prompt(
35
+ "langchain_sql_agent",
36
+ SqlAgentPromptVariables,
37
+ description="SQL agent that interacts with databases and creates queries based on natural language",
38
+ )
52
39
 
53
- Then you should query the schema of the most relevant tables.
54
- """
40
+ langfuse_handler = CallbackHandler()
55
41
 
56
42
 
57
43
  def setup_database():
@@ -99,7 +85,9 @@ async def langchain_sql_query_agent(question: str) -> str:
99
85
  tools = toolkit.get_tools()
100
86
 
101
87
  # Format system prompt with database info
102
- system_prompt = SQL_AGENT_PROMPT.format(dialect=db.dialect, top_k=5)
88
+ system_prompt = sql_agent_prompt.compile(
89
+ SqlAgentPromptVariables(dialect=db.dialect, top_k=5)
90
+ )
103
91
 
104
92
  # Create agent
105
93
  agent = create_agent(model, tools, system_prompt=system_prompt)
@@ -134,7 +122,9 @@ async def langchain_interactive_sql_agent() -> pixie.PixieGenerator[str, str]:
134
122
  tools = toolkit.get_tools()
135
123
 
136
124
  # Format system prompt
137
- system_prompt = SQL_AGENT_PROMPT.format(dialect=db.dialect, top_k=5)
125
+ system_prompt = sql_agent_prompt.compile(
126
+ SqlAgentPromptVariables(dialect=db.dialect, top_k=5)
127
+ )
138
128
 
139
129
  # Create agent with checkpointer for conversation memory
140
130
  agent = create_agent(
@@ -11,7 +11,7 @@ Based on: https://docs.langchain.com/oss/python/langgraph/agentic-rag
11
11
  """
12
12
 
13
13
  from pydantic import BaseModel, Field
14
- from typing import Literal
14
+ from typing import Literal, cast
15
15
  from langchain.chat_models import init_chat_model
16
16
  from langchain.tools import tool
17
17
  from langchain.messages import HumanMessage
@@ -28,6 +28,36 @@ import requests
28
28
  from bs4 import BeautifulSoup
29
29
 
30
30
 
31
+ class GradePromptVariables(pixie.PromptVariables):
32
+ context: str
33
+ question: str
34
+
35
+
36
+ class RewritePromptVariables(pixie.PromptVariables):
37
+ question: str
38
+
39
+
40
+ class GeneratePromptVariables(pixie.PromptVariables):
41
+ question: str
42
+ context: str
43
+
44
+
45
+ rag_grade_prompt = pixie.create_prompt(
46
+ "rag_grade_documents",
47
+ GradePromptVariables,
48
+ description="Grades relevance of retrieved documents to user questions",
49
+ )
50
+ rag_rewrite_prompt = pixie.create_prompt(
51
+ "rag_rewrite_question",
52
+ RewritePromptVariables,
53
+ description="Rewrites questions to improve semantic understanding",
54
+ )
55
+ rag_generate_prompt = pixie.create_prompt(
56
+ "rag_generate_answer",
57
+ GeneratePromptVariables,
58
+ description="Generates concise answers from retrieved context",
59
+ )
60
+
31
61
  langfuse_handler = CallbackHandler()
32
62
 
33
63
 
@@ -102,14 +132,6 @@ def create_rag_graph(retriever, model):
102
132
  description="Relevance score: 'yes' if relevant, or 'no' if not relevant"
103
133
  )
104
134
 
105
- GRADE_PROMPT = (
106
- "You are a grader assessing relevance of a retrieved document to a user question. \n "
107
- "Here is the retrieved document: \n\n {context} \n\n"
108
- "Here is the user question: {question} \n"
109
- "If the document contains keyword(s) or semantic meaning related to the user question, grade it as relevant. \n"
110
- "Give a binary score 'yes' or 'no' score to indicate whether the document is relevant to the question."
111
- )
112
-
113
135
  grader_model = init_chat_model("gpt-4o", temperature=0)
114
136
 
115
137
  # Conditional edge: Grade documents
@@ -117,10 +139,12 @@ def create_rag_graph(retriever, model):
117
139
  state: MessagesState,
118
140
  ) -> Literal["generate_answer", "rewrite_question"]:
119
141
  """Determine whether the retrieved documents are relevant to the question."""
120
- question = state["messages"][0].content
121
- context = state["messages"][-1].content
142
+ question = cast(str, state["messages"][0].content)
143
+ context = cast(str, state["messages"][-1].content)
122
144
 
123
- prompt = GRADE_PROMPT.format(question=question, context=context)
145
+ prompt = rag_grade_prompt.compile(
146
+ GradePromptVariables(question=question, context=context)
147
+ )
124
148
  response = grader_model.with_structured_output(GradeDocuments).invoke(
125
149
  [{"role": "user", "content": prompt}],
126
150
  config={"callbacks": [langfuse_handler]},
@@ -133,20 +157,11 @@ def create_rag_graph(retriever, model):
133
157
  return "rewrite_question"
134
158
 
135
159
  # Node: Rewrite question
136
- REWRITE_PROMPT = (
137
- "Look at the input and try to reason about the underlying semantic intent / meaning.\n"
138
- "Here is the initial question:"
139
- "\n ------- \n"
140
- "{question}"
141
- "\n ------- \n"
142
- "Formulate an improved question:"
143
- )
144
-
145
160
  def rewrite_question(state: MessagesState):
146
161
  """Rewrite the original user question."""
147
162
  messages = state["messages"]
148
- question = messages[0].content
149
- prompt = REWRITE_PROMPT.format(question=question)
163
+ question = cast(str, messages[0].content)
164
+ prompt = rag_rewrite_prompt.compile(RewritePromptVariables(question=question))
150
165
  response = model.invoke(
151
166
  [{"role": "user", "content": prompt}],
152
167
  config={"callbacks": [langfuse_handler]},
@@ -154,20 +169,13 @@ def create_rag_graph(retriever, model):
154
169
  return {"messages": [HumanMessage(content=response.content)]}
155
170
 
156
171
  # Node: Generate answer
157
- GENERATE_PROMPT = (
158
- "You are an assistant for question-answering tasks. "
159
- "Use the following pieces of retrieved context to answer the question. "
160
- "If you don't know the answer, just say that you don't know. "
161
- "Use three sentences maximum and keep the answer concise.\n"
162
- "Question: {question} \n"
163
- "Context: {context}"
164
- )
165
-
166
172
  def generate_answer(state: MessagesState):
167
173
  """Generate an answer."""
168
- question = state["messages"][0].content
169
- context = state["messages"][-1].content
170
- prompt = GENERATE_PROMPT.format(question=question, context=context)
174
+ question = cast(str, state["messages"][0].content)
175
+ context = cast(str, state["messages"][-1].content)
176
+ prompt = rag_generate_prompt.compile(
177
+ GeneratePromptVariables(question=question, context=context)
178
+ )
171
179
  response = model.invoke(
172
180
  [{"role": "user", "content": prompt}],
173
181
  config={"callbacks": [langfuse_handler]},
@@ -22,6 +22,21 @@ from langfuse.langchain import CallbackHandler
22
22
  import pixie
23
23
 
24
24
 
25
+ class LanggraphSqlPromptVariables(pixie.PromptVariables):
26
+ dialect: str
27
+
28
+
29
+ langgraph_sql_generate_prompt = pixie.create_prompt(
30
+ "langgraph_sql_generate_query",
31
+ LanggraphSqlPromptVariables,
32
+ description="Generates SQL queries from natural language questions",
33
+ )
34
+ langgraph_sql_check_prompt = pixie.create_prompt(
35
+ "langgraph_sql_check_query",
36
+ LanggraphSqlPromptVariables,
37
+ description="Reviews and validates SQL queries for common mistakes",
38
+ )
39
+
25
40
  langfuse_handler = CallbackHandler()
26
41
 
27
42
 
@@ -84,22 +99,11 @@ def create_sql_graph(db: SQLDatabase, model):
84
99
  return {"messages": [response]}
85
100
 
86
101
  # Node: Generate query
87
- generate_query_prompt = f"""
88
- You are an agent designed to interact with a SQL database.
89
- Given an input question, create a syntactically correct {db.dialect} query to run,
90
- then look at the results of the query and return the answer. Unless the user
91
- specifies a specific number of examples they wish to obtain, always limit your
92
- query to at most 5 results.
93
-
94
- You can order the results by a relevant column to return the most interesting
95
- examples in the database. Never query for all the columns from a specific table,
96
- only ask for the relevant columns given the question.
97
-
98
- DO NOT make any DML statements (INSERT, UPDATE, DELETE, DROP etc.) to the database.
99
- """
100
-
101
102
  def generate_query(state: MessagesState):
102
- system_message = {"role": "system", "content": generate_query_prompt}
103
+ generate_query_prompt_text = langgraph_sql_generate_prompt.compile(
104
+ LanggraphSqlPromptVariables(dialect=db.dialect)
105
+ )
106
+ system_message = {"role": "system", "content": generate_query_prompt_text}
103
107
  llm_with_tools = model.bind_tools([run_query_tool])
104
108
  response = llm_with_tools.invoke(
105
109
  [system_message] + state["messages"],
@@ -108,28 +112,13 @@ DO NOT make any DML statements (INSERT, UPDATE, DELETE, DROP etc.) to the databa
108
112
  return {"messages": [response]}
109
113
 
110
114
  # Node: Check query
111
- check_query_prompt = f"""
112
- You are a SQL expert with a strong attention to detail.
113
- Double check the {db.dialect} query for common mistakes, including:
114
- - Using NOT IN with NULL values
115
- - Using UNION when UNION ALL should have been used
116
- - Using BETWEEN for exclusive ranges
117
- - Data type mismatch in predicates
118
- - Properly quoting identifiers
119
- - Using the correct number of arguments for functions
120
- - Casting to the correct data type
121
- - Using the proper columns for joins
122
-
123
- If there are any of the above mistakes, rewrite the query. If there are no mistakes,
124
- just reproduce the original query.
125
-
126
- You will call the appropriate tool to execute the query after running this check.
127
- """
128
-
129
115
  def check_query(state: MessagesState):
130
116
  from langchain.messages import AIMessage as AI
131
117
 
132
- system_message = {"role": "system", "content": check_query_prompt}
118
+ check_query_prompt_text = langgraph_sql_check_prompt.compile(
119
+ LanggraphSqlPromptVariables(dialect=db.dialect)
120
+ )
121
+ system_message = {"role": "system", "content": check_query_prompt_text}
133
122
  last_message = state["messages"][-1]
134
123
  # Only AIMessage has tool_calls
135
124
  if isinstance(last_message, AI) and last_message.tool_calls:
@@ -26,10 +26,23 @@ from agents import (
26
26
  function_tool,
27
27
  handoff,
28
28
  )
29
- from agents.extensions.handoff_prompt import RECOMMENDED_PROMPT_PREFIX
30
29
  import pixie
31
30
 
32
31
 
32
+ faq_agent_prompt = pixie.create_prompt(
33
+ "airline_faq_agent",
34
+ description="FAQ agent that answers customer questions using knowledge base lookup",
35
+ )
36
+ seat_booking_agent_prompt = pixie.create_prompt(
37
+ "airline_seat_booking_agent",
38
+ description="Seat booking agent that updates flight seat assignments",
39
+ )
40
+ triage_agent_prompt = pixie.create_prompt(
41
+ "airline_triage_agent",
42
+ description="Triage agent that delegates customer inquiries to appropriate specialists",
43
+ )
44
+
45
+
33
46
  # ============================================================================
34
47
  # CONTEXT
35
48
  # ============================================================================
@@ -134,62 +147,58 @@ async def on_seat_booking_handoff(
134
147
 
135
148
 
136
149
  # ============================================================================
137
- # AGENTS
150
+ # AGENTS (Lazy initialization to avoid compile() at module import time)
138
151
  # ============================================================================
139
152
 
140
- faq_agent = Agent[AirlineAgentContext](
141
- name="FAQ Agent",
142
- handoff_description="A helpful agent that can answer questions about the airline.",
143
- instructions=f"""{RECOMMENDED_PROMPT_PREFIX}
153
+ _faq_agent: Agent[AirlineAgentContext] | None = None
154
+ _seat_booking_agent: Agent[AirlineAgentContext] | None = None
155
+ _triage_agent: Agent[AirlineAgentContext] | None = None
156
+ _agents_initialized: bool = False
144
157
 
145
- You are an FAQ agent. If you are speaking to a customer, you probably were
146
- transferred to from the triage agent.
147
158
 
148
- Use the following routine to support the customer.
159
+ def _initialize_agents() -> None:
160
+ """Initialize all agents with proper handoffs."""
161
+ global _faq_agent, _seat_booking_agent, _triage_agent, _agents_initialized
149
162
 
150
- # Routine
151
- 1. Identify the last question asked by the customer.
152
- 2. Use the faq lookup tool to answer the question. Do not rely on your own knowledge.
153
- 3. If you cannot answer the question, transfer back to the triage agent.""",
154
- tools=[faq_lookup_tool],
155
- )
163
+ if _agents_initialized:
164
+ return
156
165
 
157
- seat_booking_agent = Agent[AirlineAgentContext](
158
- name="Seat Booking Agent",
159
- handoff_description="A helpful agent that can update a seat on a flight.",
160
- instructions=f"""{RECOMMENDED_PROMPT_PREFIX}
166
+ _faq_agent = Agent[AirlineAgentContext](
167
+ name="FAQ Agent",
168
+ handoff_description="A helpful agent that can answer questions about the airline.",
169
+ instructions=faq_agent_prompt.compile(),
170
+ tools=[faq_lookup_tool],
171
+ )
161
172
 
162
- You are a seat booking agent. If you are speaking to a customer, you probably were
163
- transferred to from the triage agent.
173
+ _seat_booking_agent = Agent[AirlineAgentContext](
174
+ name="Seat Booking Agent",
175
+ handoff_description="A helpful agent that can update a seat on a flight.",
176
+ instructions=seat_booking_agent_prompt.compile(),
177
+ tools=[update_seat],
178
+ )
164
179
 
165
- Use the following routine to support the customer.
180
+ _triage_agent = Agent[AirlineAgentContext](
181
+ name="Triage Agent",
182
+ handoff_description="A triage agent that can delegate a customer's request to the appropriate agent.",
183
+ instructions=triage_agent_prompt.compile(),
184
+ handoffs=[
185
+ _faq_agent,
186
+ handoff(agent=_seat_booking_agent, on_handoff=on_seat_booking_handoff),
187
+ ],
188
+ )
166
189
 
167
- # Routine
168
- 1. Ask for their confirmation number.
169
- 2. Ask the customer what their desired seat number is.
170
- 3. Use the update seat tool to update the seat on the flight.
190
+ # Set up bidirectional handoffs
191
+ _faq_agent.handoffs.append(_triage_agent)
192
+ _seat_booking_agent.handoffs.append(_triage_agent)
171
193
 
172
- If the customer asks a question that is not related to the routine, transfer back to the
173
- triage agent.""",
174
- tools=[update_seat],
175
- )
194
+ _agents_initialized = True
176
195
 
177
- triage_agent = Agent[AirlineAgentContext](
178
- name="Triage Agent",
179
- handoff_description="A triage agent that can delegate a customer's request to the appropriate agent.",
180
- instructions=(
181
- f"{RECOMMENDED_PROMPT_PREFIX} "
182
- "You are a helpful triaging agent. You can use your tools to delegate questions to other appropriate agents."
183
- ),
184
- handoffs=[
185
- faq_agent,
186
- handoff(agent=seat_booking_agent, on_handoff=on_seat_booking_handoff),
187
- ],
188
- )
189
196
 
190
- # Set up bidirectional handoffs
191
- faq_agent.handoffs.append(triage_agent)
192
- seat_booking_agent.handoffs.append(triage_agent)
197
+ def get_triage_agent() -> Agent[AirlineAgentContext]:
198
+ """Get the triage agent (entry point)."""
199
+ _initialize_agents()
200
+ assert _triage_agent is not None
201
+ return _triage_agent
193
202
 
194
203
 
195
204
  @pixie.app
@@ -210,7 +219,7 @@ async def openai_agents_airline_customer_service() -> pixie.PixieGenerator[str,
210
219
  Receives:
211
220
  User messages via InputRequired
212
221
  """
213
- current_agent: Agent[AirlineAgentContext] = triage_agent
222
+ current_agent: Agent[AirlineAgentContext] = get_triage_agent()
214
223
  input_items: list[TResponseInputItem] = []
215
224
  context = AirlineAgentContext()
216
225