Python: Add samples syntax checking with pyright (#3710)

* Add samples syntax checking with pyright

- Add pyrightconfig.samples.json with relaxed type checking but import validation
- Add samples-syntax poe task to check samples for syntax and import errors
- Add samples-syntax to check and pre-commit-check tasks
- Fix 78 sample errors:
  - Update workflow builder imports to use agent_framework_orchestrations
  - Change content type isinstance checks to content.type comparisons
  - Use Content factory methods instead of removed content type classes
  - Fix TypedDict access patterns for Annotation
  - Fix various API mismatches (normalize_messages, ChatMessage.text, role)

* fixed a bunch of samples and tweaks to pre-commit

* updated lock

* updated lock

* fixes

* added lint to samples
This commit is contained in:
Eduard van Valkenburg
2026-02-07 08:10:47 +01:00
committed by GitHub
Unverified
parent 74ac470a56
commit 390f93344c
83 changed files with 606 additions and 498 deletions
@@ -18,7 +18,7 @@ from typing import Annotated, Any
import uvicorn
# Agent Framework imports
from agent_framework import AgentResponseUpdate, ChatAgent, ChatMessage, FunctionResultContent, Role, tool
from agent_framework import AgentResponseUpdate, ChatAgent, ChatMessage, tool
from agent_framework.azure import AzureOpenAIChatClient
# Agent Framework ChatKit integration
@@ -281,7 +281,7 @@ class WeatherChatKitServer(ChatKitServer[dict[str, Any]]):
title_prompt = [
ChatMessage(
role=Role.USER,
role="user",
text=(
f"Generate a very short, concise title (max 40 characters) for a conversation "
f"that starts with:\n\n{conversation_context}\n\n"
@@ -332,7 +332,6 @@ class WeatherChatKitServer(ChatKitServer[dict[str, Any]]):
runs the agent, converts the response back to ChatKit events using stream_agent_response,
and creates interactive weather widgets when weather data is queried.
"""
from agent_framework import FunctionResultContent
if input_user_message is None:
logger.debug("Received None user message, skipping")
@@ -375,7 +374,7 @@ class WeatherChatKitServer(ChatKitServer[dict[str, Any]]):
# Check for function results in the update
if update.contents:
for content in update.contents:
if isinstance(content, FunctionResultContent):
if content.type == "function_result":
result = content.result
# Check if it's a WeatherResponse (string subclass with weather_data attribute)
@@ -458,7 +457,7 @@ class WeatherChatKitServer(ChatKitServer[dict[str, Any]]):
weather_data: WeatherData | None = None
# Create an agent message asking about the weather
agent_messages = [ChatMessage(role=Role.USER, text=f"What's the weather in {city_label}?")]
agent_messages = [ChatMessage(role="user", text=f"What's the weather in {city_label}?")]
logger.debug(f"Processing weather query: {agent_messages[0].text}")
@@ -472,7 +471,7 @@ class WeatherChatKitServer(ChatKitServer[dict[str, Any]]):
# Check for function results in the update
if update.contents:
for content in update.contents:
if isinstance(content, FunctionResultContent):
if content.type == "function_result":
result = content.result
# Check if it's a WeatherResponse (string subclass with weather_data attribute)
@@ -563,7 +562,7 @@ async def chatkit_endpoint(request: Request):
@app.post("/upload/{attachment_id}")
async def upload_file(attachment_id: str, file: UploadFile = File(...)):
async def upload_file(attachment_id: str, file: Annotated[UploadFile, File()]):
"""Handle file upload for two-phase upload.
The client POSTs the file bytes here after creating the attachment
@@ -585,7 +584,7 @@ async def upload_file(attachment_id: str, file: UploadFile = File(...)):
attachment = await data_store.load_attachment(attachment_id, {"user_id": DEFAULT_USER_ID})
# Clear the upload_url since upload is complete
attachment.upload_url = None
attachment.upload_url = None # type: ignore[union-attr]
# Save the updated attachment back to the store
await data_store.save_attachment(attachment, {"user_id": DEFAULT_USER_ID})
@@ -1,7 +1,7 @@
# Copyright (c) Microsoft. All rights reserved.
from agent_framework import ConcurrentBuilder
from agent_framework.azure import AzureOpenAIChatClient
from agent_framework_orchestrations import ConcurrentBuilder
from azure.ai.agentserver.agentframework import from_agent_framework
from azure.identity import DefaultAzureCredential # pyright: ignore[reportUnknownVariableType]
@@ -21,7 +21,7 @@ def search_hotels(
guests: Annotated[int, Field(description="Number of guests.")] = 2,
) -> str:
"""Search for available hotels based on location and dates.
Returns:
JSON string containing search results with hotel details including name, rating,
price, distance to landmarks, amenities, and availability.
@@ -88,7 +88,7 @@ def get_hotel_details(
hotel_name: Annotated[str, Field(description="Name of the hotel to get details for.")],
) -> str:
"""Get detailed information about a specific hotel.
Returns:
JSON string containing detailed hotel information including description,
check-in/out times, cancellation policy, reviews, and nearby attractions.
@@ -167,7 +167,7 @@ def search_flights(
passengers: Annotated[int, Field(description="Number of passengers.")] = 1,
) -> str:
"""Search for available flights between two locations.
Returns:
JSON string containing flight search results with details including flight numbers,
airlines, departure/arrival times, prices, durations, and baggage allowances.
@@ -289,7 +289,7 @@ def get_flight_details(
flight_number: Annotated[str, Field(description="Flight number (e.g., 'AF007' or 'DL264').")],
) -> str:
"""Get detailed information about a specific flight.
Returns:
JSON string containing detailed flight information including airline, aircraft type,
departure/arrival airports and times, gates, terminals, duration, and amenities.
@@ -331,7 +331,7 @@ def search_activities(
category: Annotated[str | None, Field(description="Activity category (e.g., 'Sightseeing', 'Culture', 'Culinary').")] = None,
) -> str:
"""Search for available activities and attractions at a destination.
Returns:
JSON string containing activity search results with details including name, category,
duration, price, rating, description, availability, and booking requirements.
@@ -440,10 +440,7 @@ def search_activities(
}
]
if category:
activities = [act for act in all_activities if act["category"] == category]
else:
activities = all_activities
activities = [act for act in all_activities if act["category"] == category] if category else all_activities
else:
activities = [
{
@@ -473,7 +470,7 @@ def get_activity_details(
activity_name: Annotated[str, Field(description="Name of the activity to get details for.")],
) -> str:
"""Get detailed information about a specific activity.
Returns:
JSON string containing detailed activity information including description, duration,
price, included items, meeting point, what to bring, cancellation policy, and reviews.
@@ -552,7 +549,7 @@ def confirm_booking(
customer_info: Annotated[dict, Field(description="Customer information including name and email.")],
) -> str:
"""Confirm a booking reservation.
Returns:
JSON string containing confirmation details including confirmation number,
booking status, customer information, and next steps.
@@ -587,9 +584,9 @@ def check_hotel_availability(
rooms: Annotated[int, Field(description="Number of rooms needed.")] = 1,
) -> str:
"""Check availability for hotel rooms.
Sample Date format: "December 15, 2025"
Returns:
JSON string containing availability status, available rooms count, price per night,
and last checked timestamp.
@@ -621,9 +618,9 @@ def check_flight_availability(
passengers: Annotated[int, Field(description="Number of passengers.")] = 1,
) -> str:
"""Check availability for flight seats.
Sample Date format: "December 15, 2025"
Returns:
JSON string containing availability status, available seats count, price per passenger,
and last checked timestamp.
@@ -654,9 +651,9 @@ def check_activity_availability(
participants: Annotated[int, Field(description="Number of participants.")] = 1,
) -> str:
"""Check availability for activity bookings.
Sample Date format: "December 16, 2025"
Returns:
JSON string containing availability status, available spots count, price per person,
and last checked timestamp.
@@ -688,7 +685,7 @@ def process_payment(
booking_reference: Annotated[str, Field(description="Booking reference number for the payment.")],
) -> str:
"""Process payment for a booking.
Returns:
JSON string containing payment result with transaction ID, status, amount, currency,
payment method details, and receipt URL.
@@ -718,7 +715,7 @@ def validate_payment_method(
payment_method: Annotated[dict, Field(description="Payment method to validate (type, number, expiry, cvv).")],
) -> str:
"""Validate payment method details.
Returns:
JSON string containing validation result with is_valid flag, payment method type,
validation messages, supported currencies, and processing fee information.
@@ -154,19 +154,19 @@ async def run_workflow_with_response_tracking(query: str, chat_client: AzureAICl
"""
if chat_client is None:
try:
# Create AIProjectClient with the correct API version for V2 prompt agents
project_client = AIProjectClient(
endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
credential=credential,
api_version="2025-11-15-preview",
)
async with DefaultAzureCredential() as credential:
# Create AIProjectClient with the correct API version for V2 prompt agents
project_client = AIProjectClient(
endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
credential=credential,
api_version="2025-11-15-preview",
)
async with (
DefaultAzureCredential() as credential,
project_client,
AzureAIClient(project_client=project_client, credential=credential) as client,
):
return await _run_workflow_with_client(query, client)
async with (
project_client,
AzureAIClient(project_client=project_client, credential=credential) as client,
):
return await _run_workflow_with_client(query, client)
except Exception as e:
print(f"Error during workflow execution: {e}")
raise
@@ -369,27 +369,36 @@ async def _process_workflow_events(events, conversation_ids, response_ids):
def _track_agent_ids(event, agent, response_ids, conversation_ids):
"""Track agent response and conversation IDs - supporting multiple responses per agent."""
if isinstance(event.data, AgentResponseUpdate):
if (
isinstance(event.data, AgentResponseUpdate)
and hasattr(event.data, "raw_representation")
and event.data.raw_representation
):
# Check for conversation_id and response_id from raw_representation
# V2 API stores conversation_id directly on raw_representation (ChatResponseUpdate)
if hasattr(event.data, "raw_representation") and event.data.raw_representation:
raw = event.data.raw_representation
raw = event.data.raw_representation
# Try conversation_id directly on raw representation
if hasattr(raw, "conversation_id") and raw.conversation_id:
# Try conversation_id directly on raw representation
if (
hasattr(raw, "conversation_id")
and raw.conversation_id # type: ignore[union-attr]
and raw.conversation_id not in conversation_ids[agent] # type: ignore[union-attr]
):
# Only add if not already in the list
conversation_ids[agent].append(raw.conversation_id) # type: ignore[union-attr]
# Extract response_id from the OpenAI event (available from first event)
if hasattr(raw, "raw_representation") and raw.raw_representation: # type: ignore[union-attr]
openai_event = raw.raw_representation # type: ignore[union-attr]
# Check if event has response object with id
if (
hasattr(openai_event, "response")
and hasattr(openai_event.response, "id")
and openai_event.response.id not in response_ids[agent]
):
# Only add if not already in the list
if raw.conversation_id not in conversation_ids[agent]:
conversation_ids[agent].append(raw.conversation_id)
# Extract response_id from the OpenAI event (available from first event)
if hasattr(raw, "raw_representation") and raw.raw_representation:
openai_event = raw.raw_representation
# Check if event has response object with id
if hasattr(openai_event, "response") and hasattr(openai_event.response, "id"):
# Only add if not already in the list
if openai_event.response.id not in response_ids[agent]:
response_ids[agent].append(openai_event.response.id)
response_ids[agent].append(openai_event.response.id)
async def create_and_run_workflow():
@@ -29,7 +29,7 @@ def print_section(title: str):
async def run_workflow():
"""Execute the multi-agent travel planning workflow.
Returns:
Dictionary containing workflow data with agent response IDs
"""