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
@@ -2,7 +2,7 @@
import asyncio
from agent_framework import HostedMCPTool, HostedWebSearchTool, TextReasoningContent, UsageContent
from agent_framework import HostedMCPTool, HostedWebSearchTool
from agent_framework.anthropic import AnthropicChatOptions, AnthropicClient
"""
@@ -40,9 +40,9 @@ async def main() -> None:
print("Agent: ", end="", flush=True)
async for chunk in agent.run(query, stream=True):
for content in chunk.contents:
if isinstance(content, TextReasoningContent):
if content.type == "text_reasoning":
print(f"\033[32m{content.text}\033[0m", end="", flush=True)
if isinstance(content, UsageContent):
if content.type == "usage":
print(f"\n\033[34m[Usage so far: {content.usage_details}]\033[0m\n", end="", flush=True)
if chunk.text:
print(chunk.text, end="", flush=True)
@@ -2,7 +2,7 @@
import asyncio
from agent_framework import HostedMCPTool, HostedWebSearchTool, TextReasoningContent, UsageContent
from agent_framework import HostedMCPTool, HostedWebSearchTool
from agent_framework.anthropic import AnthropicClient
from anthropic import AsyncAnthropicFoundry
@@ -51,9 +51,9 @@ async def main() -> None:
print("Agent: ", end="", flush=True)
async for chunk in agent.run(query, stream=True):
for content in chunk.contents:
if isinstance(content, TextReasoningContent):
if content.type == "text_reasoning":
print(f"\033[32m{content.text}\033[0m", end="", flush=True)
if isinstance(content, UsageContent):
if content.type == "usage":
print(f"\n\033[34m[Usage so far: {content.usage_details}]\033[0m\n", end="", flush=True)
if chunk.text:
print(chunk.text, end="", flush=True)
@@ -4,7 +4,7 @@ import asyncio
import logging
from pathlib import Path
from agent_framework import HostedCodeInterpreterTool, HostedFileContent
from agent_framework import Content, HostedCodeInterpreterTool
from agent_framework.anthropic import AnthropicChatOptions, AnthropicClient
logger = logging.getLogger(__name__)
@@ -52,7 +52,7 @@ async def main() -> None:
query = "Create a presentation about renewable energy with 5 slides"
print(f"User: {query}")
print("Agent: ", end="", flush=True)
files: list[HostedFileContent] = []
files: list[Content] = []
async for chunk in agent.run(query, stream=True):
for content in chunk.contents:
match content.type:
@@ -6,11 +6,10 @@ from pathlib import Path
from agent_framework import (
AgentResponseUpdate,
Annotation,
ChatAgent,
CitationAnnotation,
Content,
HostedCodeInterpreterTool,
HostedFileContent,
TextContent,
)
from agent_framework.azure import AzureAIProjectAgentProvider
from azure.identity.aio import AzureCliCredential
@@ -34,19 +33,17 @@ QUERY = (
)
async def download_container_files(
file_contents: list[CitationAnnotation | HostedFileContent], agent: ChatAgent
) -> list[Path]:
async def download_container_files(file_contents: list[Annotation | Content], agent: ChatAgent) -> list[Path]:
"""Download container files using the OpenAI containers API.
Code interpreter generates files in containers, which require both file_id
and container_id to download. The container_id is stored in additional_properties.
This function works for both streaming (HostedFileContent) and non-streaming
(CitationAnnotation) responses.
This function works for both streaming (Content with type="hosted_file") and non-streaming
(Annotation) responses.
Args:
file_contents: List of CitationAnnotation or HostedFileContent objects
file_contents: List of Annotation or Content objects
containing file_id and container_id.
agent: The ChatAgent instance with access to the AzureAIClient.
@@ -64,28 +61,36 @@ async def download_container_files(
print(f"\nDownloading {len(file_contents)} container file(s) to {output_dir.absolute()}...")
# Access the OpenAI client from AzureAIClient
openai_client = agent.chat_client.client
openai_client = agent.chat_client.client # type: ignore[attr-defined]
downloaded_files: list[Path] = []
for content in file_contents:
file_id = content.file_id
# Handle both Annotation (TypedDict) and Content objects
if isinstance(content, dict): # Annotation TypedDict
file_id = content.get("file_id")
additional_props = content.get("additional_properties", {})
url = content.get("url")
else: # Content object
file_id = content.file_id
additional_props = content.additional_properties or {}
url = content.uri
# Extract container_id from additional_properties
if not content.additional_properties or "container_id" not in content.additional_properties:
if not additional_props or "container_id" not in additional_props:
print(f" File {file_id}: ✗ Missing container_id")
continue
container_id = content.additional_properties["container_id"]
container_id = additional_props["container_id"]
# Extract filename based on content type
if isinstance(content, CitationAnnotation):
filename = content.url or f"{file_id}.txt"
if isinstance(content, dict): # Annotation TypedDict
filename = url or f"{file_id}.txt"
# Extract filename from sandbox URL if present (e.g., sandbox:/mnt/data/sample.txt)
if filename.startswith("sandbox:"):
filename = filename.split("/")[-1]
else: # HostedFileContent
filename = content.additional_properties.get("filename") or f"{file_id}.txt"
else: # Content
filename = additional_props.get("filename") or f"{file_id}.txt"
output_path = output_dir / filename
@@ -133,17 +138,18 @@ async def non_streaming_example() -> None:
print(f"Agent: {result.text}\n")
# Check for annotations in the response
annotations_found: list[CitationAnnotation] = []
annotations_found: list[Annotation] = []
# AgentResponse has messages property, which contains ChatMessage objects
for message in result.messages:
for content in message.contents:
if content.type == "text" and content.annotations:
for annotation in content.annotations:
if isinstance(annotation, CitationAnnotation) and annotation.file_id:
if annotation.get("file_id"):
annotations_found.append(annotation)
print(f"Found file annotation: file_id={annotation.file_id}")
if annotation.additional_properties and "container_id" in annotation.additional_properties:
print(f" container_id={annotation.additional_properties['container_id']}")
print(f"Found file annotation: file_id={annotation['file_id']}")
additional_props = annotation.get("additional_properties", {})
if additional_props and "container_id" in additional_props:
print(f" container_id={additional_props['container_id']}")
if annotations_found:
print(f"SUCCESS: Found {len(annotations_found)} file annotation(s)")
@@ -174,7 +180,7 @@ async def streaming_example() -> None:
)
print(f"User: {QUERY}\n")
file_contents_found: list[HostedFileContent] = []
file_contents_found: list[Content] = []
text_chunks: list[str] = []
async for update in agent.run(QUERY, stream=True):
@@ -185,11 +191,11 @@ async def streaming_example() -> None:
text_chunks.append(content.text)
if content.annotations:
for annotation in content.annotations:
if isinstance(annotation, CitationAnnotation) and annotation.file_id:
print(f"Found streaming CitationAnnotation: file_id={annotation.file_id}")
elif isinstance(content, HostedFileContent):
if annotation.get("file_id"):
print(f"Found streaming annotation: file_id={annotation['file_id']}")
elif content.type == "hosted_file":
file_contents_found.append(content)
print(f"Found streaming HostedFileContent: file_id={content.file_id}")
print(f"Found streaming hosted_file: file_id={content.file_id}")
if content.additional_properties and "container_id" in content.additional_properties:
print(f" container_id={content.additional_properties['container_id']}")
@@ -49,9 +49,9 @@ async def non_streaming_example() -> None:
for content in message.contents:
if content.type == "text" and content.annotations:
for annotation in content.annotations:
if annotation.file_id:
annotations_found.append(annotation.file_id)
print(f"Found file annotation: file_id={annotation.file_id}")
if annotation.get("file_id"):
annotations_found.append(annotation["file_id"])
print(f"Found file annotation: file_id={annotation['file_id']}")
if annotations_found:
print(f"SUCCESS: Found {len(annotations_found)} file annotation(s)")
@@ -86,9 +86,9 @@ async def streaming_example() -> None:
text_chunks.append(content.text)
if content.annotations:
for annotation in content.annotations:
if annotation.file_id:
annotations_found.append(annotation.file_id)
print(f"Found streaming annotation: file_id={annotation.file_id}")
if annotation.get("file_id"):
annotations_found.append(annotation["file_id"])
print(f"Found streaming annotation: file_id={annotation['file_id']}")
elif content.type == "hosted_file":
file_ids_found.append(content.file_id)
print(f"Found streaming HostedFileContent: file_id={content.file_id}")
@@ -4,7 +4,7 @@ import asyncio
import os
from pathlib import Path
from agent_framework import HostedFileSearchTool, HostedVectorStoreContent
from agent_framework import Content, HostedFileSearchTool
from agent_framework.azure import AzureAIProjectAgentProvider
from azure.ai.agents.aio import AgentsClient
from azure.ai.agents.models import FileInfo, VectorStore
@@ -46,7 +46,7 @@ async def main() -> None:
print(f"Created vector store, vector store ID: {vector_store.id}")
# 2. Create file search tool with uploaded resources
file_search_tool = HostedFileSearchTool(inputs=[HostedVectorStoreContent(vector_store_id=vector_store.id)])
file_search_tool = HostedFileSearchTool(inputs=[Content.from_hosted_vector_store(vector_store_id=vector_store.id)])
# 3. Create an agent with file search capabilities using the provider
agent = await provider.create_agent(
@@ -3,7 +3,7 @@
import asyncio
from typing import Any
from agent_framework import SupportsAgentRun, AgentResponse, AgentThread, ChatMessage, HostedMCPTool
from agent_framework import AgentResponse, AgentThread, ChatMessage, HostedMCPTool, SupportsAgentRun
from agent_framework.azure import AzureAIProjectAgentProvider
from azure.identity.aio import AzureCliCredential
@@ -5,7 +5,6 @@ import os
from agent_framework import (
HostedCodeInterpreterTool,
HostedFileContent,
)
from agent_framework.azure import AzureAIAgentsProvider
from azure.ai.agents.aio import AgentsClient
@@ -63,7 +62,7 @@ async def main() -> None:
for content in chunk.contents:
if content.type == "text":
print(content.text, end="", flush=True)
elif content.type == "hosted_file" and isinstance(content, HostedFileContent):
elif content.type == "hosted_file" and content.file_id:
file_ids.append(content.file_id)
print(f"\n[File generated: {content.file_id}]")
@@ -4,7 +4,7 @@ import asyncio
import os
from pathlib import Path
from agent_framework import HostedFileSearchTool, HostedVectorStoreContent
from agent_framework import Content, HostedFileSearchTool
from agent_framework.azure import AzureAIAgentsProvider
from azure.ai.agents.aio import AgentsClient
from azure.ai.agents.models import FileInfo, VectorStore
@@ -46,7 +46,7 @@ async def main() -> None:
print(f"Created vector store, vector store ID: {vector_store.id}")
# 2. Create file search tool with uploaded resources
file_search_tool = HostedFileSearchTool(inputs=[HostedVectorStoreContent(vector_store_id=vector_store.id)])
file_search_tool = HostedFileSearchTool(inputs=[Content.from_hosted_vector_store(vector_store_id=vector_store.id)])
# 3. Create an agent with file search capabilities
agent = await provider.create_agent(
@@ -3,7 +3,7 @@
import asyncio
from typing import Any
from agent_framework import SupportsAgentRun, AgentResponse, AgentThread, HostedMCPTool
from agent_framework import AgentResponse, AgentThread, HostedMCPTool, SupportsAgentRun
from agent_framework.azure import AzureAIAgentsProvider
from azure.identity.aio import AzureCliCredential
@@ -5,10 +5,10 @@ from datetime import datetime, timezone
from typing import Any
from agent_framework import (
SupportsAgentRun,
AgentThread,
HostedMCPTool,
HostedWebSearchTool,
SupportsAgentRun,
tool,
)
from agent_framework.azure import AzureAIAgentsProvider
@@ -2,7 +2,7 @@
import asyncio
from agent_framework import ChatAgent, HostedFileSearchTool, HostedVectorStoreContent
from agent_framework import ChatAgent, Content, HostedFileSearchTool
from agent_framework.azure import AzureOpenAIResponsesClient
from azure.identity import AzureCliCredential
@@ -22,7 +22,7 @@ Prerequisites:
# Helper functions
async def create_vector_store(client: AzureOpenAIResponsesClient) -> tuple[str, HostedVectorStoreContent]:
async def create_vector_store(client: AzureOpenAIResponsesClient) -> tuple[str, Content]:
"""Create a vector store with sample documents."""
file = await client.client.files.create(
file=("todays_weather.txt", b"The weather today is sunny with a high of 75F."), purpose="assistants"
@@ -35,7 +35,7 @@ async def create_vector_store(client: AzureOpenAIResponsesClient) -> tuple[str,
if result.last_error is not None:
raise Exception(f"Vector store file processing failed with status: {result.last_error.message}")
return file.id, HostedVectorStoreContent(vector_store_id=vector_store.id)
return file.id, Content.from_hosted_vector_store(vector_store_id=vector_store.id)
async def delete_vector_store(client: AzureOpenAIResponsesClient, file_id: str, vector_store_id: str) -> None:
@@ -15,7 +15,7 @@ Azure OpenAI Responses Client, including user approval workflows for function ca
"""
if TYPE_CHECKING:
from agent_framework import SupportsAgentRun, AgentThread
from agent_framework import AgentThread, SupportsAgentRun
async def handle_approvals_without_thread(query: str, agent: "SupportsAgentRun"):
@@ -12,7 +12,7 @@ from agent_framework import (
ChatMessage,
Content,
Role,
TextContent,
normalize_messages,
)
"""
@@ -88,12 +88,14 @@ class EchoAgent(BaseAgent):
) -> AgentResponse:
"""Non-streaming implementation."""
# Normalize input messages to a list
normalized_messages = self._normalize_messages(messages)
normalized_messages = normalize_messages(messages)
if not normalized_messages:
response_message = ChatMessage(
role=Role.ASSISTANT,
contents=[Content.from_text(text="Hello! I'm a custom echo agent. Send me a message and I'll echo it back.")],
contents=[
Content.from_text(text="Hello! I'm a custom echo agent. Send me a message and I'll echo it back.")
],
)
else:
# For simplicity, echo the last user message
@@ -120,7 +122,7 @@ class EchoAgent(BaseAgent):
) -> AsyncIterable[AgentResponseUpdate]:
"""Streaming implementation."""
# Normalize input messages to a list
normalized_messages = self._normalize_messages(messages)
normalized_messages = normalize_messages(messages)
if not normalized_messages:
response_text = "Hello! I'm a custom echo agent. Send me a message and I'll echo it back."
@@ -5,7 +5,15 @@ from collections.abc import Awaitable, Callable
from random import randint
from typing import Annotated
from agent_framework import ChatAgent, ChatContext, ChatMessage, ChatResponse, Role, chat_middleware, tool
from agent_framework import (
ChatAgent,
ChatContext,
ChatMessage,
ChatResponse,
MiddlewareTermination,
chat_middleware,
tool,
)
from agent_framework.openai import OpenAIResponsesClient
from pydantic import Field
@@ -39,7 +47,7 @@ async def security_and_override_middleware(
context.result = ChatResponse(
messages=[
ChatMessage(
role=Role.ASSISTANT,
role="assistant",
text="I cannot process requests containing sensitive information. "
"Please rephrase your question without including passwords, secrets, or other "
"sensitive data.",
@@ -48,8 +56,7 @@ async def security_and_override_middleware(
)
# Set terminate flag to stop execution
context.terminate = True
return
raise MiddlewareTermination
# Continue to next middleware or AI execution
await next(context)
@@ -70,7 +70,7 @@ async def main() -> None:
# Show information about the generated image
for message in result.messages:
for content in message.contents:
if content.type == "image_generation" and content.outputs:
if content.type == "image_generation_tool_result" and content.outputs:
for output in content.outputs:
if output.type in ("data", "uri") and output.uri:
show_image_info(output.uri)
@@ -32,7 +32,7 @@ async def main() -> None:
print(f"Result: {result}\n")
for message in result.messages:
code_blocks = [c for c in message.contents if c.type == "code_interpreter_tool_input"]
code_blocks = [c for c in message.contents if c.type == "code_interpreter_tool_call"]
outputs = [c for c in message.contents if c.type == "code_interpreter_tool_result"]
if code_blocks:
code_inputs = code_blocks[0].inputs or []
@@ -14,7 +14,7 @@ OpenAI Responses Client, including user approval workflows for function call sec
"""
if TYPE_CHECKING:
from agent_framework import SupportsAgentRun, AgentThread
from agent_framework import AgentThread, SupportsAgentRun
async def handle_approvals_without_thread(query: str, agent: "SupportsAgentRun"):