chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,107 @@
|
||||
"""Human-in-the-loop example with a custom rejection message.
|
||||
|
||||
This example is intentionally minimal:
|
||||
1. A single sensitive tool requires human approval.
|
||||
2. The first turn always issues that tool call.
|
||||
3. ``tool_error_formatter`` defines the universal fallback message shape.
|
||||
4. A per-call ``rejection_message`` passed to ``state.reject(...)`` overrides that fallback.
|
||||
5. The example prints both the tool output and the assistant's final reply.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
|
||||
from agents import (
|
||||
Agent,
|
||||
ModelSettings,
|
||||
RunConfig,
|
||||
Runner,
|
||||
ToolErrorFormatterArgs,
|
||||
function_tool,
|
||||
)
|
||||
from examples.auto_mode import confirm_with_fallback
|
||||
|
||||
|
||||
async def tool_error_formatter(args: ToolErrorFormatterArgs[None]) -> str | None:
|
||||
"""Build the universal fallback output message for rejected tool calls."""
|
||||
if args.kind != "approval_rejected":
|
||||
return None
|
||||
# The default message is "Tool execution was not approved."
|
||||
return "Publish action was canceled because approval was rejected."
|
||||
|
||||
|
||||
@function_tool(needs_approval=True)
|
||||
async def publish_announcement(title: str, body: str) -> str:
|
||||
"""Simulate publishing an announcement to users."""
|
||||
return f"Published announcement '{title}' with body: {body}"
|
||||
|
||||
|
||||
def _find_formatter_output(result: object) -> str | None:
|
||||
items = getattr(result, "new_items", None)
|
||||
if not isinstance(items, list):
|
||||
return None
|
||||
|
||||
for item in items:
|
||||
if getattr(item, "type", None) != "tool_call_output_item":
|
||||
continue
|
||||
output = getattr(item, "output", None)
|
||||
if isinstance(output, str):
|
||||
return output
|
||||
return None
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
agent = Agent(
|
||||
name="Operations Assistant",
|
||||
instructions=(
|
||||
"When a user asks to publish an announcement, call the publish_announcement tool directly. "
|
||||
"Do not ask the user for approval in plain text; runtime approvals handle that. "
|
||||
"If the tool call is rejected, respond with the exact rejection message and nothing else."
|
||||
),
|
||||
model_settings=ModelSettings(tool_choice="publish_announcement"),
|
||||
tools=[publish_announcement],
|
||||
)
|
||||
run_config = RunConfig(tool_error_formatter=tool_error_formatter)
|
||||
# ``tool_error_formatter`` is the universal fallback for approval rejects.
|
||||
# A specific ``rejection_message`` passed to ``state.reject(...)`` below overrides it.
|
||||
|
||||
result = await Runner.run(
|
||||
agent,
|
||||
"Please publish an announcement titled 'Office maintenance' with body "
|
||||
"'The office will close at 6 PM today.'",
|
||||
run_config=run_config,
|
||||
)
|
||||
|
||||
while result.interruptions:
|
||||
print("\nApproval required:")
|
||||
state = result.to_state()
|
||||
for interruption in result.interruptions:
|
||||
print(f"- Tool: {interruption.name}")
|
||||
print(f" Arguments: {interruption.arguments}")
|
||||
approved = confirm_with_fallback(
|
||||
"Approve this tool call? [y/N]: ",
|
||||
default=False,
|
||||
)
|
||||
if approved:
|
||||
state.approve(interruption)
|
||||
else:
|
||||
# This per-call rejection message takes precedence over ``tool_error_formatter``.
|
||||
state.reject(
|
||||
interruption,
|
||||
rejection_message=(
|
||||
"Publish action was canceled because the reviewer denied approval."
|
||||
),
|
||||
)
|
||||
|
||||
result = await Runner.run(agent, state, run_config=run_config)
|
||||
|
||||
formatter_output = _find_formatter_output(result)
|
||||
if formatter_output:
|
||||
print("\nFormatter output:")
|
||||
print(formatter_output)
|
||||
|
||||
print("\nFinal output:")
|
||||
print(result.final_output)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
Reference in New Issue
Block a user