220 lines
6.2 KiB
Python
220 lines
6.2 KiB
Python
import asyncio
|
|
import json
|
|
import sys
|
|
from collections.abc import Mapping
|
|
from typing import Annotated, Any
|
|
|
|
from agents import (
|
|
Agent,
|
|
ModelSettings,
|
|
Runner,
|
|
ToolSearchTool,
|
|
function_tool,
|
|
tool_namespace,
|
|
trace,
|
|
)
|
|
|
|
CUSTOMER_PROFILES = {
|
|
"customer_42": {
|
|
"customer_id": "customer_42",
|
|
"full_name": "Avery Chen",
|
|
"tier": "enterprise",
|
|
}
|
|
}
|
|
|
|
OPEN_ORDERS = {
|
|
"customer_42": [
|
|
{"order_id": "ord_1042", "status": "awaiting fulfillment"},
|
|
{"order_id": "ord_1049", "status": "pending approval"},
|
|
]
|
|
}
|
|
|
|
INVOICE_STATUSES = {
|
|
"inv_2001": "paid",
|
|
}
|
|
|
|
SHIPPING_ETAS = {
|
|
"ZX-123": "2026-03-06 14:00 JST",
|
|
}
|
|
|
|
SHIPPING_CREDIT_BALANCES = {
|
|
"customer_42": "$125.00",
|
|
}
|
|
|
|
|
|
@function_tool(defer_loading=True)
|
|
def get_customer_profile(
|
|
customer_id: Annotated[str, "The CRM customer identifier to look up."],
|
|
) -> str:
|
|
"""Fetch a CRM customer profile."""
|
|
return json.dumps(CUSTOMER_PROFILES[customer_id], indent=2)
|
|
|
|
|
|
@function_tool(defer_loading=True)
|
|
def list_open_orders(
|
|
customer_id: Annotated[str, "The CRM customer identifier to look up."],
|
|
) -> str:
|
|
"""List open orders for a customer."""
|
|
return json.dumps(OPEN_ORDERS.get(customer_id, []), indent=2)
|
|
|
|
|
|
@function_tool(defer_loading=True)
|
|
def get_invoice_status(
|
|
invoice_id: Annotated[str, "The invoice identifier to look up."],
|
|
) -> str:
|
|
"""Look up the status of an invoice."""
|
|
return INVOICE_STATUSES.get(invoice_id, "unknown")
|
|
|
|
|
|
@function_tool(defer_loading=True)
|
|
def get_shipping_eta(
|
|
tracking_number: Annotated[str, "The shipment tracking number to look up."],
|
|
) -> str:
|
|
"""Look up a shipment ETA by tracking number."""
|
|
return SHIPPING_ETAS.get(tracking_number, "unavailable")
|
|
|
|
|
|
@function_tool(defer_loading=True)
|
|
def get_shipping_credit_balance(
|
|
customer_id: Annotated[str, "The customer account identifier to look up."],
|
|
) -> str:
|
|
"""Look up the available shipping credit balance for a customer."""
|
|
return SHIPPING_CREDIT_BALANCES.get(customer_id, "$0.00")
|
|
|
|
|
|
crm_tools = tool_namespace(
|
|
name="crm",
|
|
description="CRM tools for customer lookups.",
|
|
tools=[get_customer_profile, list_open_orders],
|
|
)
|
|
|
|
billing_tools = tool_namespace(
|
|
name="billing",
|
|
description="Billing tools for invoice lookups.",
|
|
tools=[get_invoice_status],
|
|
)
|
|
|
|
namespaced_agent = Agent(
|
|
name="Operations assistant",
|
|
model="gpt-5.6-sol",
|
|
instructions=(
|
|
"For customer questions in this example, load the full `crm` namespace with no query "
|
|
"filter before calling tools. "
|
|
"Do not search `billing` unless the user asks about invoices."
|
|
),
|
|
model_settings=ModelSettings(parallel_tool_calls=False),
|
|
tools=[*crm_tools, *billing_tools, ToolSearchTool()],
|
|
)
|
|
|
|
top_level_agent = Agent(
|
|
name="Shipping assistant",
|
|
model="gpt-5.6-sol",
|
|
instructions=(
|
|
"For ETA questions in this example, search `get_shipping_eta` before calling tools. "
|
|
"Do not search `get_shipping_credit_balance` unless the user asks about shipping credits."
|
|
),
|
|
model_settings=ModelSettings(parallel_tool_calls=False),
|
|
tools=[get_shipping_eta, get_shipping_credit_balance, ToolSearchTool()],
|
|
)
|
|
|
|
|
|
def loaded_paths(result: Any) -> list[str]:
|
|
paths: set[str] = set()
|
|
|
|
for item in result.new_items:
|
|
if item.type != "tool_search_output_item":
|
|
continue
|
|
|
|
raw_tools = (
|
|
item.raw_item.get("tools")
|
|
if isinstance(item.raw_item, Mapping)
|
|
else getattr(item.raw_item, "tools", None)
|
|
)
|
|
if not isinstance(raw_tools, list):
|
|
continue
|
|
|
|
for raw_tool in raw_tools:
|
|
tool_payload = (
|
|
raw_tool
|
|
if isinstance(raw_tool, Mapping)
|
|
else (
|
|
raw_tool.model_dump(exclude_unset=True)
|
|
if callable(getattr(raw_tool, "model_dump", None))
|
|
else None
|
|
)
|
|
)
|
|
if not isinstance(tool_payload, Mapping):
|
|
continue
|
|
|
|
tool_type = tool_payload.get("type")
|
|
if tool_type == "namespace":
|
|
path = tool_payload.get("name")
|
|
elif tool_type == "function":
|
|
path = tool_payload.get("name")
|
|
else:
|
|
path = tool_payload.get("server_label")
|
|
|
|
if isinstance(path, str) and path:
|
|
paths.add(path)
|
|
|
|
return sorted(paths)
|
|
|
|
|
|
def print_result(title: str, result: Any, registered_paths: list[str]) -> None:
|
|
loaded = loaded_paths(result)
|
|
untouched = [path for path in registered_paths if path not in loaded]
|
|
|
|
print(f"## {title}")
|
|
print("### Final output")
|
|
print(result.final_output)
|
|
print("\n### Loaded paths")
|
|
print(f"- registered: {', '.join(registered_paths)}")
|
|
print(f"- loaded: {', '.join(loaded) if loaded else 'none'}")
|
|
print(f"- untouched: {', '.join(untouched) if untouched else 'none'}")
|
|
print("\n### Relevant items")
|
|
for item in result.new_items:
|
|
if item.type in {"tool_search_call_item", "tool_search_output_item", "tool_call_item"}:
|
|
print(f"- {item.type}: {item.raw_item}")
|
|
print()
|
|
|
|
|
|
async def run_namespaced_example() -> None:
|
|
result = await Runner.run(
|
|
namespaced_agent,
|
|
"Look up customer_42 and list their open orders.",
|
|
)
|
|
print_result(
|
|
"Tool search with namespaces",
|
|
result,
|
|
registered_paths=["crm", "billing"],
|
|
)
|
|
|
|
|
|
async def run_top_level_example() -> None:
|
|
result = await Runner.run(
|
|
top_level_agent,
|
|
"Can you get my ETA for tracking number ZX-123?",
|
|
)
|
|
print_result(
|
|
"Tool search with top-level deferred tools",
|
|
result,
|
|
registered_paths=["get_shipping_eta", "get_shipping_credit_balance"],
|
|
)
|
|
|
|
|
|
async def main() -> None:
|
|
mode = sys.argv[1] if len(sys.argv) > 1 else "all"
|
|
|
|
if mode not in {"all", "namespace", "top-level"}:
|
|
raise SystemExit(f"Unknown mode: {mode}. Expected one of: all, namespace, top-level.")
|
|
|
|
with trace("Tool search example"):
|
|
if mode in {"all", "namespace"}:
|
|
await run_namespaced_example()
|
|
if mode in {"all", "top-level"}:
|
|
await run_top_level_example()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
asyncio.run(main())
|