Files
openai--openai-agents-python/examples/memory/file_hitl_example.py
T
2026-07-13 12:39:17 +08:00

153 lines
5.6 KiB
Python

"""
File-backed session example with human-in-the-loop tool approval.
This mirrors the JS `file-hitl.ts` sample: a session persisted on disk and tools that
require approval before execution.
"""
from __future__ import annotations
import asyncio
import json
from typing import Any
from agents import Agent, Runner, function_tool
from agents.run_context import RunContextWrapper
from agents.run_state import RunState
from examples.auto_mode import confirm_with_fallback, input_with_fallback, is_auto_mode
from .file_session import FileSession
async def main() -> None:
user_context = {"user_id": "101"}
customer_directory: dict[str, str] = {
"101": (
"Customer Kaz S. (tier gold) can be reached at +1-415-555-AAAA. "
"Notes: Prefers SMS follow ups and values concise summaries."
),
"104": (
"Customer Yu S. (tier platinum) can be reached at +1-415-555-BBBB. "
"Notes: Recently reported sync issues. Flagged for a proactive onboarding call."
),
"205": (
"Customer Ken S. (tier standard) can be reached at +1-415-555-CCCC. "
"Notes: Interested in automation tutorials sent last week."
),
}
lookup_customer_profile = create_lookup_customer_profile_tool(directory=customer_directory)
instructions = (
"You assist support agents. For every user turn you must call lookup_customer_profile. "
"If a tool reports a transient failure, request approval and retry the same call once before "
"responding. Keep responses under three sentences."
)
agent = Agent(
name="File HITL assistant",
instructions=instructions,
tools=[lookup_customer_profile],
)
session = FileSession(dir="examples/memory/tmp")
session_id = await session.get_session_id()
print(f"Session id: {session_id}")
print("Enter a message to chat with the agent. Submit an empty line to exit.")
auto_mode = is_auto_mode()
saved_state = await session.load_state_json()
if saved_state:
print("Found saved run state. Resuming pending interruptions before new input.")
try:
state = await RunState.from_json(agent, saved_state, context_override=user_context)
result = await Runner.run(agent, state, session=session)
while result.interruptions:
state = result.to_state()
for interruption in result.interruptions:
args = format_tool_arguments(interruption)
approved = await prompt_yes_no(
f"Agent {interruption.agent.name} wants to call {interruption.name} with {args or 'no arguments'}"
)
if approved:
state.approve(interruption)
print("Approved tool call.")
else:
state.reject(interruption)
print("Rejected tool call.")
result = await Runner.run(agent, state, session=session)
await session.save_state_json(result.to_state().to_json())
reply = result.final_output or "[No final output produced]"
print(f"Assistant (resumed): {reply}\n")
except Exception as exc: # noqa: BLE001
print(f"Failed to resume saved state: {exc}. Starting a new session.")
while True:
if auto_mode:
user_message = input_with_fallback("You: ", "Summarize the customer profile.")
else:
print("You: ", end="", flush=True)
loop = asyncio.get_event_loop()
user_message = await loop.run_in_executor(None, input)
if not user_message.strip():
break
result = await Runner.run(agent, user_message, session=session, context=user_context)
while result.interruptions:
state = result.to_state()
for interruption in result.interruptions:
args = format_tool_arguments(interruption)
approved = await prompt_yes_no(
f"Agent {interruption.agent.name} wants to call {interruption.name} with {args or 'no arguments'}"
)
if approved:
state.approve(interruption)
print("Approved tool call.")
else:
state.reject(interruption)
print("Rejected tool call.")
result = await Runner.run(agent, state, session=session)
await session.save_state_json(result.to_state().to_json())
reply = result.final_output or "[No final output produced]"
print(f"Assistant: {reply}\n")
if auto_mode:
break
def create_lookup_customer_profile_tool(
*,
directory: dict[str, str],
missing_customer_message: str = "No customer found for that id.",
):
@function_tool(
name_override="lookup_customer_profile",
description_override="Look up stored profile details for a customer by their internal id.",
needs_approval=True,
)
def lookup_customer_profile(ctx: RunContextWrapper[Any]) -> str:
return directory.get(ctx.context.get("user_id"), missing_customer_message)
return lookup_customer_profile
def format_tool_arguments(interruption: Any) -> str:
args = getattr(interruption, "arguments", None)
if args is None:
return ""
if isinstance(args, str):
return args
try:
return json.dumps(args)
except Exception:
return str(args)
async def prompt_yes_no(question: str) -> bool:
return confirm_with_fallback(f"{question} (y/n): ", default=True)
if __name__ == "__main__":
asyncio.run(main())