Python: Simplify Python Poe tasks and unify package selectors (#4722)

* updated automation tasks and commands, with alias for the time being

* Restore aggregate test exclusions

Preserve the legacy all-tests scope for test --all by excluding lab and devui from the default aggregate sweep, while still allowing explicit package selection. Also ignore hidden/generated test directories such as .mypy_cache during aggregate discovery.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* updated versions in pre-commit

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
Eduard van Valkenburg
2026-03-18 19:39:11 +01:00
committed by GitHub
Unverified
parent d3d0100822
commit f48c4512d3
60 changed files with 1704 additions and 527 deletions
@@ -29,7 +29,9 @@ async def main() -> None:
client = OpenAIChatClient()
try:
task = asyncio.create_task(client.get_response(messages=[Message(role="user", text="Tell me a fantasy story.")]))
task = asyncio.create_task(
client.get_response(messages=[Message(role="user", text="Tell me a fantasy story.")])
)
await asyncio.sleep(1)
task.cancel()
await task
@@ -94,9 +94,7 @@ class EchoingChatClient(BaseChatClient[OptionsT]):
response_text = f"{response_text} {suffix}"
stream_delay_seconds = float(options.get("stream_delay_seconds", 0.05))
response_message = Message(
role="assistant", contents=[Content.from_text(response_text)]
)
response_message = Message(role="assistant", text=response_text)
response = ChatResponse(
messages=[response_message],
@@ -27,15 +27,9 @@ class KeepLastUserTurnStrategy:
group_annotation = message.additional_properties.get(GROUP_ANNOTATION_KEY)
group_id = group_annotation.get("id") if isinstance(group_annotation, dict) else None
kind = group_annotation.get("kind") if isinstance(group_annotation, dict) else None
if (
isinstance(group_id, str)
and isinstance(kind, str)
and group_id not in group_kinds
):
if isinstance(group_id, str) and isinstance(kind, str) and group_id not in group_kinds:
group_kinds[group_id] = kind
user_group_ids = [
group_id for group_id in group_ids if group_kinds.get(group_id) == "user"
]
user_group_ids = [group_id for group_id in group_ids if group_kinds.get(group_id) == "user"]
if not user_group_ids:
return False
keep_user_group_id = user_group_ids[-1]
@@ -33,9 +33,7 @@ Key components:
class TiktokenTokenizer(TokenizerProtocol):
"""TokenizerProtocol implementation backed by tiktoken's o200k_base (gpt-4.1 and up default) encoding."""
def __init__(
self, *, encoding_name: str = "o200k_base", model_name: str | None = None
) -> None:
def __init__(self, *, encoding_name: str = "o200k_base", model_name: str | None = None) -> None:
if model_name is not None:
self._encoding = tiktoken.encoding_for_model(model_name)
else:
@@ -62,10 +60,7 @@ def _build_messages() -> list[Message]:
),
Message(
role="user",
text=(
"Now provide a detailed checklist with owners, rollback "
"gates, and validation criteria."
),
text=("Now provide a detailed checklist with owners, rollback gates, and validation criteria."),
),
Message(
role="assistant",
@@ -37,9 +37,7 @@ def get_weather(
"""Get the weather for a given location."""
conditions = ["sunny", "cloudy", "rainy", "stormy"]
temperature = 53
return (
f"The weather in {location} is {conditions[0]} with a high of {temperature}°C."
)
return f"The weather in {location} is {conditions[0]} with a high of {temperature}°C."
@tool(approval_mode="never_require")
@@ -68,9 +66,7 @@ class AddExclamation(Executor):
"""Add exclamation mark to text."""
@handler
async def add_exclamation(
self, text: str, ctx: WorkflowContext[Never, str]
) -> None:
async def add_exclamation(self, text: str, ctx: WorkflowContext[Never, str]) -> None:
"""Add exclamation and yield as workflow output."""
result = f"{text}!"
await ctx.yield_output(result)
@@ -19,9 +19,7 @@ from copilot.generated.session_events import PermissionRequest
from copilot.types import PermissionRequestResult
def prompt_permission(
request: PermissionRequest, context: dict[str, str]
) -> PermissionRequestResult:
def prompt_permission(request: PermissionRequest, context: dict[str, str]) -> PermissionRequestResult:
"""Permission handler that prompts the user for approval."""
print(f"\n[Permission Request: {request.kind}]")
@@ -75,7 +75,9 @@ unit_converter_skill = Skill(
# ---------------------------------------------------------------------------
# 2. Dynamic Resources — callable function via @skill.resource
# ---------------------------------------------------------------------------
@unit_converter_skill.resource(name="conversion-policy", description="Current conversion formatting and rounding policy")
@unit_converter_skill.resource(
name="conversion-policy", description="Current conversion formatting and rounding policy"
)
def conversion_policy(**kwargs: Any) -> Any:
"""Return the current conversion policy.
@@ -148,8 +150,7 @@ async def main() -> None:
print("Converting units")
print("-" * 60)
response = await agent.run(
"How many kilometers is a marathon (26.2 miles)? "
"And how many pounds is 75 kilograms?",
"How many kilometers is a marathon (26.2 miles)? And how many pounds is 75 kilograms?",
precision=2,
)
print(f"Agent: {response}\n")
@@ -70,8 +70,7 @@ async def main() -> None:
print("Converting units")
print("-" * 60)
response = await agent.run(
"How many kilometers is a marathon (26.2 miles)? "
"And how many pounds is 75 kilograms?"
"How many kilometers is a marathon (26.2 miles)? And how many pounds is 75 kilograms?"
)
print(f"Agent: {response}\n")
@@ -137,8 +137,7 @@ async def main() -> None:
print("Converting units")
print("-" * 60)
response = await agent.run(
"How many kilometers is a marathon (26.2 miles)? "
"And how many liters is a 5-gallon bucket?"
"How many kilometers is a marathon (26.2 miles)? And how many liters is a 5-gallon bucket?"
)
print(f"Agent: {response}\n")
@@ -135,8 +135,7 @@ async def scenario_max_function_calls():
)
response = await agent.run(
"Search for the weather in Paris, London, Tokyo, "
"New York, and Sydney, and also search for best travel tips."
"Search for the weather in Paris, London, Tokyo, New York, and Sydney, and also search for best travel tips."
)
print(f" Response: {response.text[:200]}...")
print()
@@ -236,8 +235,12 @@ async def scenario_per_agent_tool_limits():
session_b = agent_b.create_session()
await agent_b.run("Look up quantum computing", session=session_b)
print(f" agent_a_lookup.invocation_count = {agent_a_lookup.invocation_count} (limit {agent_a_lookup.max_invocations})")
print(f" agent_b_lookup.invocation_count = {agent_b_lookup.invocation_count} (limit {agent_b_lookup.max_invocations})")
print(
f" agent_a_lookup.invocation_count = {agent_a_lookup.invocation_count} (limit {agent_a_lookup.max_invocations})"
)
print(
f" agent_b_lookup.invocation_count = {agent_b_lookup.invocation_count} (limit {agent_b_lookup.max_invocations})"
)
print(" → Agent A hit its limit; Agent B used 1 of 5.")
print()
@@ -254,8 +257,8 @@ async def scenario_combined():
client = OpenAIResponsesClient()
# 1. Configure the client with both iteration and function call limits.
client.function_invocation_configuration["max_iterations"] = 5 # max 5 LLM roundtrips
client.function_invocation_configuration["max_function_calls"] = 8 # max 8 total tool calls
client.function_invocation_configuration["max_iterations"] = 5 # max 5 LLM roundtrips
client.function_invocation_configuration["max_function_calls"] = 8 # max 8 total tool calls
print(f" max_iterations = {client.function_invocation_configuration['max_iterations']}")
print(f" max_function_calls = {client.function_invocation_configuration['max_function_calls']}")
@@ -47,12 +47,8 @@ async def main() -> None:
session = agent.create_session()
# Run the agent with the session; tools receive it via ctx.session.
print(
f"Agent: {await agent.run('What is the weather in London?', session=session)}"
)
print(
f"Agent: {await agent.run('What is the weather in Amsterdam?', session=session)}"
)
print(f"Agent: {await agent.run('What is the weather in London?', session=session)}")
print(f"Agent: {await agent.run('What is the weather in Amsterdam?', session=session)}")
print(f"Agent: {await agent.run('What cities did I ask about?', session=session)}")
+110 -50
View File
@@ -72,56 +72,116 @@ def _random_date_within_last_two_months() -> datetime:
def _build_invoices() -> list[Invoice]:
"""Build 10 mock invoices."""
return [
Invoice("TICKET-XYZ987", "INV789", "Contoso", _random_date_within_last_two_months(), [
Product("T-Shirts", 150, 10.00),
Product("Hats", 200, 15.00),
Product("Glasses", 300, 5.00),
]),
Invoice("TICKET-XYZ111", "INV111", "XStore", _random_date_within_last_two_months(), [
Product("T-Shirts", 2500, 12.00),
Product("Hats", 1500, 8.00),
Product("Glasses", 200, 20.00),
]),
Invoice("TICKET-XYZ222", "INV222", "Cymbal Direct", _random_date_within_last_two_months(), [
Product("T-Shirts", 1200, 14.00),
Product("Hats", 800, 7.00),
Product("Glasses", 500, 25.00),
]),
Invoice("TICKET-XYZ333", "INV333", "Contoso", _random_date_within_last_two_months(), [
Product("T-Shirts", 400, 11.00),
Product("Hats", 600, 15.00),
Product("Glasses", 700, 5.00),
]),
Invoice("TICKET-XYZ444", "INV444", "XStore", _random_date_within_last_two_months(), [
Product("T-Shirts", 800, 10.00),
Product("Hats", 500, 18.00),
Product("Glasses", 300, 22.00),
]),
Invoice("TICKET-XYZ555", "INV555", "Cymbal Direct", _random_date_within_last_two_months(), [
Product("T-Shirts", 1100, 9.00),
Product("Hats", 900, 12.00),
Product("Glasses", 1200, 15.00),
]),
Invoice("TICKET-XYZ666", "INV666", "Contoso", _random_date_within_last_two_months(), [
Product("T-Shirts", 2500, 8.00),
Product("Hats", 1200, 10.00),
Product("Glasses", 1000, 6.00),
]),
Invoice("TICKET-XYZ777", "INV777", "XStore", _random_date_within_last_two_months(), [
Product("T-Shirts", 1900, 13.00),
Product("Hats", 1300, 16.00),
Product("Glasses", 800, 19.00),
]),
Invoice("TICKET-XYZ888", "INV888", "Cymbal Direct", _random_date_within_last_two_months(), [
Product("T-Shirts", 2200, 11.00),
Product("Hats", 1700, 8.50),
Product("Glasses", 600, 21.00),
]),
Invoice("TICKET-XYZ999", "INV999", "Contoso", _random_date_within_last_two_months(), [
Product("T-Shirts", 1400, 10.50),
Product("Hats", 1100, 9.00),
Product("Glasses", 950, 12.00),
]),
Invoice(
"TICKET-XYZ987",
"INV789",
"Contoso",
_random_date_within_last_two_months(),
[
Product("T-Shirts", 150, 10.00),
Product("Hats", 200, 15.00),
Product("Glasses", 300, 5.00),
],
),
Invoice(
"TICKET-XYZ111",
"INV111",
"XStore",
_random_date_within_last_two_months(),
[
Product("T-Shirts", 2500, 12.00),
Product("Hats", 1500, 8.00),
Product("Glasses", 200, 20.00),
],
),
Invoice(
"TICKET-XYZ222",
"INV222",
"Cymbal Direct",
_random_date_within_last_two_months(),
[
Product("T-Shirts", 1200, 14.00),
Product("Hats", 800, 7.00),
Product("Glasses", 500, 25.00),
],
),
Invoice(
"TICKET-XYZ333",
"INV333",
"Contoso",
_random_date_within_last_two_months(),
[
Product("T-Shirts", 400, 11.00),
Product("Hats", 600, 15.00),
Product("Glasses", 700, 5.00),
],
),
Invoice(
"TICKET-XYZ444",
"INV444",
"XStore",
_random_date_within_last_two_months(),
[
Product("T-Shirts", 800, 10.00),
Product("Hats", 500, 18.00),
Product("Glasses", 300, 22.00),
],
),
Invoice(
"TICKET-XYZ555",
"INV555",
"Cymbal Direct",
_random_date_within_last_two_months(),
[
Product("T-Shirts", 1100, 9.00),
Product("Hats", 900, 12.00),
Product("Glasses", 1200, 15.00),
],
),
Invoice(
"TICKET-XYZ666",
"INV666",
"Contoso",
_random_date_within_last_two_months(),
[
Product("T-Shirts", 2500, 8.00),
Product("Hats", 1200, 10.00),
Product("Glasses", 1000, 6.00),
],
),
Invoice(
"TICKET-XYZ777",
"INV777",
"XStore",
_random_date_within_last_two_months(),
[
Product("T-Shirts", 1900, 13.00),
Product("Hats", 1300, 16.00),
Product("Glasses", 800, 19.00),
],
),
Invoice(
"TICKET-XYZ888",
"INV888",
"Cymbal Direct",
_random_date_within_last_two_months(),
[
Product("T-Shirts", 2200, 11.00),
Product("Hats", 1700, 8.50),
Product("Glasses", 600, 21.00),
],
),
Invoice(
"TICKET-XYZ999",
"INV999",
"Contoso",
_random_date_within_last_two_months(),
[
Product("T-Shirts", 1400, 10.50),
Product("Hats", 1100, 9.00),
Product("Glasses", 950, 12.00),
],
),
]
+1 -1
View File
@@ -62,7 +62,7 @@ Users can create a `.env` file in the `python/` directory based on `.env.example
## Syntax Checking
Run `uv run poe samples-syntax` to check samples for syntax errors and missing imports from `agent_framework`. This uses a relaxed pyright configuration that validates imports without strict type checking.
Run `uv run poe pyright -S` to check samples for syntax errors and missing imports from `agent_framework`. This uses a relaxed pyright configuration that validates imports without strict type checking.
Some samples depend on external packages (e.g., `azure.ai.agentserver.agentframework`, `microsoft_agents`) that are not installed in the dev environment. These are excluded in `pyrightconfig.samples.json`. When adding or modifying these excluded samples, add them to the exclude list and manually verify they have no import errors from `agent_framework` packages by temporarily removing them from the exclude list and running the check.